Skip to content

Commit

Permalink
added sign up form and captcha
Browse files Browse the repository at this point in the history
  • Loading branch information
OomsOoms committed Oct 2, 2024
1 parent dc436bf commit ea4ca29
Show file tree
Hide file tree
Showing 15 changed files with 604 additions and 22 deletions.
441 changes: 435 additions & 6 deletions client/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
"@hcaptcha/react-hcaptcha": "^1.11.0",
"@mantine/notifications": "^7.13.1",
"@react-oauth/google": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
Expand Down
3 changes: 2 additions & 1 deletion client/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UserProvider } from './context/userContext.jsx';
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/home";
import LoginForm from "./features/authentication";
import { RegisterForm, LoginForm } from "./features/authentication";
import './assets/App.css'


Expand All @@ -13,6 +13,7 @@ export default function App() {
<Route path="/">
<Route index element={<Home />} />
<Route path="login" element={<LoginForm />} />
<Route path="register" element={<RegisterForm />} />
</Route>
</Routes>
</BrowserRouter>
Expand Down
30 changes: 30 additions & 0 deletions client/src/components/ui/Captcha.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import HCaptcha from '@hcaptcha/react-hcaptcha';
import { showNotification } from '@mantine/notifications';

const Captcha = ({ show, onToken }) => {
if (!show) {
return null;
}

return (
<>
<HCaptcha
sitekey={import.meta.env.MODE === 'development'
? '10000000-ffff-ffff-ffff-000000000001'
: '798876a4-47b6-480a-b92c-45bedfc45272'}
onVerify={onToken}
onExpire={() => onToken('')}
onError={(err) => {
onToken('');
showNotification({
title: 'Error',
message: 'Cannot verify captcha',
});
console.error(err);
}}
/>
</>
);
};

export default Captcha;
2 changes: 1 addition & 1 deletion client/src/context/userContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const UserProvider = ({ children }) => {
useEffect(() => {
const checkAuthStatus = async () => {
try {
const response = await fetch('https://api.nettleship.net/api/auth/status', {
const response = await fetch('/api/auth/status', {
credentials: 'include', // Include cookies if needed
});
if (response.ok) {
Expand Down
65 changes: 65 additions & 0 deletions client/src/features/authentication/components/RegisterForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useState, useContext } from 'react';
import { UserContext } from '../../../context/userContext.jsx';
import { useRegister } from '../hooks/useRegister.js';
import Captcha from '../../../components/ui/Captcha.jsx';

const RegisterForm = () => {
const { user, loading } = useContext(UserContext);
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [captchaToken, setCaptchaToken] = useState('');
const { handleRegister, loading: registerLoading, error } = useRegister();

const onSubmit = (e) => {
e.preventDefault(); // Prevent the default form submission
handleRegister(username, email, password, captchaToken);
}

if (loading) {
return <div>Loading...</div>;
}

if (user) {
window.location.href = '/';
}

return (
<form onSubmit={onSubmit}>
<div>
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<Captcha show={true} onToken={setCaptchaToken} />
<button type="submit" disabled={registerLoading}>
{registerLoading ? 'Signing up...' : 'Signup'}
</button>
{error && <div>{error}</div>}
</form>
);
};

export default RegisterForm;
3 changes: 2 additions & 1 deletion client/src/features/authentication/hooks/useLogin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react';
import { login } from '../services/authService';
import { login } from '../services/loginService';

export const useLogin = () => {
const [loading, setLoading] = useState(false);
Expand All @@ -11,6 +11,7 @@ export const useLogin = () => {
try {
const response = await login(username, password);
if (!response.ok) {
console.log(response);
return setError('Invalid credentials');
}
if (response.ok) {
Expand Down
27 changes: 27 additions & 0 deletions client/src/features/authentication/hooks/useRegister.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useState } from 'react';
import { register } from '../services/registerService';

export const useRegister = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const handleRegister = async (username, email, password, hCaptchaToken) => {
setLoading(true);
setError(null);
try {
const response = await register(username, email, password, hCaptchaToken);
if (!response.ok) {
return response;
}
if (response.ok) {
window.location.href = '/';
}
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};

return { handleRegister, loading, error };
};
7 changes: 6 additions & 1 deletion client/src/features/authentication/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
import LoginForm from './components/LoginForm';
export default LoginForm;
import RegisterForm from './components/RegisterForm';

export {
LoginForm,
RegisterForm,
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const login = async (username, password) => {
try {
const response = await fetch('https://api.nettleship.net/api/auth/login', {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
Expand Down
22 changes: 22 additions & 0 deletions client/src/features/authentication/services/registerService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const register = async (username, email, password, hCaptchaToken) => {
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, email, password, hCaptchaToken }),
credentials: 'include' // Include credentials (cookies)
});

if (response.ok) {
return { ok: true, message: 'Registration successful' };
} else {
const data = await response.json();
return { ok: false, errors: data.errors };
}
} catch (error) {
console.error('There was a problem with the fetch operation:', error);
return { ok: false, errors: [{ path: 'server', msg: 'Server error' }] };
}
};
2 changes: 1 addition & 1 deletion client/src/pages/home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Home = () => {

const handleLogout = async () => {
try {
const response = await fetch('https://api.nettleship.net/api/auth/logout', {
const response = await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include', // Include cookies if needed
});
Expand Down
16 changes: 8 additions & 8 deletions client/vite.config.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react-swc'

// https://vitejs.dev/config/
export default defineConfig(() => {
// eslint-disable-next-line no-undef
const env = loadEnv('', process.cwd(), '')

export default defineConfig(({ command, mode }) => {
// Load env file based on `mode` in the current working directory.
// Set the third parameter to '' to load all env regardless of the `VITE_` prefix.
const env = loadEnv(mode, process.cwd(), '')
return {
// vite config
//define: {
// __APP_ENV__: JSON.stringify(env.APP_ENV),
//}, not sure what this does but ill keep it here for now
define: {
__APP_ENV__: JSON.stringify(env.APP_ENV),
},
server: {
port: 3000,
proxy: {
'/api': {
target: env.API_DOMAIN, // should make this an env variable
target: env.VITE_API_DOMAIN, // should make this an env variable
changeOrigin: true,
secure: env.NODE_ENV === 'production',
},
Expand Down
2 changes: 1 addition & 1 deletion server/src/api/validations/user.validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const registerUser = [
.withMessage('Password must be at least 8 characters long')
.isLength({ max: 128 })
.withMessage('Password must be less than 128 characters long')
.matches(/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,128}$/)
.withMessage('Password must contain at least one uppercase letter, one lowercase letter, and one number'),
validateRequest,
verifyCaptcha,
Expand Down
2 changes: 1 addition & 1 deletion server/src/api/validations/validateRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const validateRequest = (req, res, next) => {
if (!errors.isEmpty()) {
const error = process.env.NODE_ENV === 'production' ? 'Invalid request parameters' : errors.array();

res.status(400).json({ message: error });
res.status(400).json({ errors: error });
} else {
next();
}
Expand Down

0 comments on commit ea4ca29

Please sign in to comment.