Skip to content

Commit

Permalink
Merge pull request #101 from Arquisoft/90-user-policies
Browse files Browse the repository at this point in the history
90 user policies
  • Loading branch information
Mister-Mario authored Apr 16, 2024
2 parents 6a31274 + aeda7db commit 099084d
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 16 deletions.
8 changes: 7 additions & 1 deletion webapp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"react-icons": "^5.0.1",
"react-router-dom": "^6.22.3",
"react-scripts": "^5.0.1",
"web-vitals": "^3.5.1"
"web-vitals": "^3.5.1",
"zxcvbn": "^4.4.2"
},
"scripts": {
"start": "react-scripts start",
Expand Down
87 changes: 79 additions & 8 deletions webapp/src/components/loginAndRegistration/AddUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import axios from 'axios';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import zxcvbn from "zxcvbn";


const AddUser = () => {
Expand All @@ -14,26 +15,87 @@ const AddUser = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [repeatPassword, setRepeatPassword] = useState('');
const [passwordStrength, setPasswordStrength] = useState(undefined);
const [passwordStrengthText, setPasswordStrengthText] = useState('');
const [submitError, setSubmitError] = useState('');


const handleSubmit = async (event) => {
event.preventDefault();
try {
//TODO: Add more validations
if(password === repeatPassword){ //User put the same password
//Validations
//TODO: email validation
if(password !== repeatPassword){
//User put the same password
setSubmitError("addUser.error_passwords_no_match");
} else if(/\s/.test(password)){
//User put spaces in password
setSubmitError("addUser.error_password_spaces");
} else if(password.length < 8){
//Password too short
setSubmitError("addUser.error_password_minimum_length");
} else if(password.length > 64){
//Password too long
setSubmitError("addUser.error_password_maximum_length");
} else if(/\s/.test(username)){
//Spaces in username
setSubmitError("addUser.error_username_spaces");
} else{
//Continue
setSubmitError('');
const response = await axios.post(apiUrl, { username, password });
console.log("Registered user: " + response.data.username);
navigate('/login');
}
else{
//TODO: Show some errors to the user
}

} catch (error) {
if(error.response.data.error === "Username already in use"){ //TODO: Improve
setSubmitError("addUser.error_username_in_use");
}
console.error('Error adding user:', error);
}
};

//Possible email validation
/**
const validateEmail = (email) => {
return String(email)
.toLowerCase()
.match(
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
);
};
*/

const handlePasswordChange = (e) => {
const newPassword = e.target.value;
setPassword(newPassword);

const newStrength = zxcvbn(newPassword);

switch(newStrength.score){
case 0:
setPasswordStrengthText("addUser.very_weak_password");
break;
case 1:
setPasswordStrengthText("addUser.very_weak_password");
break;
case 2:
setPasswordStrengthText("addUser.weak_password");
break;
case 3:
setPasswordStrengthText("addUser.good_password");
break;
case 4:
setPasswordStrengthText("addUser.strong_password");
break;
default:
setPasswordStrengthText("addUser.very_weak_password");
break;
}
setPasswordStrength(newStrength);
};

return (
<div className="general">
<div className="card">
Expand All @@ -51,17 +113,26 @@ const AddUser = () => {
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="input-box">
<div className="input-box-password-register">
<p>{t("addUser.password_placeholder")}:</p>
<input
name = "password"
type="password"
placeholder={t("addUser.password_placeholder")}
required
value={password}
onChange={(e) => setPassword(e.target.value)}
onChange={handlePasswordChange}
/>
</div>
<div className="password-strength-meter">
<span>
{t(passwordStrengthText.toString())}
</span>
<progress
value={passwordStrength === undefined? 0 : passwordStrength.score}
max="4"
/>
</div>
<div className="input-box">
<p>{t("addUser.repeat_password_placeholder")}:</p>
<input
Expand All @@ -73,7 +144,7 @@ const AddUser = () => {
onChange={(e) => setRepeatPassword(e.target.value)}
/>
</div>

{submitError && <p style={{ color: 'red' }}>{t(submitError)}</p>}
<button type="submit">{t("addUser.register_button")}</button>

<LinkLogin />
Expand Down
54 changes: 51 additions & 3 deletions webapp/src/components/loginAndRegistration/AddUser.test.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import AddUser from './AddUser';
import { BrowserRouter as Router } from 'react-router-dom';
import axios from 'axios';
import { BrowserRouter as Router } from 'react-router-dom';

// Mocking useTranslation hook
jest.mock('react-i18next', () => ({
useTranslation: () => ({ t: key => key }),
}));

// Mocking axios to simulate an error response
jest.mock('axios');

describe('<AddUser />', () => {
test('renders the AddUser component', () => {

beforeEach(() => {
render(
<Router>
<AddUser />
</Router>
);
});

test('renders the AddUser component', () => {

expect(screen.getByText('addUser.title')).toBeInTheDocument();
expect(screen.getByText('addUser.username_placeholder:')).toBeInTheDocument();
Expand All @@ -24,6 +32,46 @@ describe('<AddUser />', () => {
expect(screen.getByText('addUser.login_link')).toBeInTheDocument();

});

const fillFormAndSubmit = (username, password, repeatPassword) => {
const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder');
fireEvent.change(usernameInput, { target: { value: username } });

const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder');
fireEvent.change(passwordInput, { target: { value: password } });

const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder');
fireEvent.change(repeatPasswordInput, { target: { value: repeatPassword } });

const submitButton = screen.getByText('addUser.register_button');
fireEvent.click(submitButton);
};

test('displays correct error messages', async () => {
//Passwords do not match
fillFormAndSubmit('username', '12345678', '123456789');
expect(screen.getByText('addUser.error_passwords_no_match')).toBeInTheDocument();
//Password with spaces
fillFormAndSubmit('username', '1234 5678', '1234 5678');
expect(screen.getByText('addUser.error_password_spaces')).toBeInTheDocument();
//Password too short
fillFormAndSubmit('username', '1234567', '1234567');
expect(screen.getByText('addUser.error_password_minimum_length')).toBeInTheDocument();
//Password too long
fillFormAndSubmit('username', '01234567890123456789012345678901234567890123456789012345678901234', '01234567890123456789012345678901234567890123456789012345678901234');
expect(screen.getByText('addUser.error_password_maximum_length')).toBeInTheDocument();
//Username with spaces
fillFormAndSubmit('user name', '12345678', '12345678');
expect(screen.getByText('addUser.error_username_spaces')).toBeInTheDocument();
//Username in use
axios.post.mockRejectedValue({ response: { data: { error: 'Username already in use' } } });
fillFormAndSubmit('existing_user', '12345678', '12345678');
await waitFor(() => {
expect(screen.getByText('addUser.error_username_in_use')).toBeInTheDocument();
});
expect(axios.post).toHaveBeenCalledWith(expect.any(String), { username: 'existing_user', password: '12345678' });
});

});


38 changes: 38 additions & 0 deletions webapp/src/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,44 @@
padding: 10px;
}

.input-box-password-register {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 0px;
}

.input-box-password-register p {
font-weight: bold;
margin-right: 10%;
width: 150px;
text-align: right;
}

.input-box-password-register input {
flex: 1;
height: 40px;
background: rgb(255, 255, 255);
border: 2px solid gray;
border-radius: 10px;
font-size: 18px;
color: black;
padding: 10px;
padding-bottom: 0px;
}

.password-strength-meter {
display: flex;
flex-direction: row;
align-items: left;
gap: 20px;
margin-top: 0px;
}

.password-strength-meter span {
font-size: 14px;
color: rgba(255, 255, 255, 0.5);
}

.button-register, .button-login {
appearance: none;
Expand Down
12 changes: 11 additions & 1 deletion webapp/src/translations/en/global.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,17 @@
"password_placeholder": "Password",
"repeat_password_placeholder": "Repeat password",
"register_button": "Register",
"login_link": "Do you have an account? Login here."
"login_link": "Do you have an account? Login here.",
"very_weak_password": "Very weak password",
"weak_password": "Weak password",
"good_password": "Good password",
"strong_password": "Strong password",
"error_passwords_no_match": "Passwords do not match",
"error_password_spaces": "Password cannot contain spaces",
"error_username_spaces": "Username cannot contain spaces",
"error_password_minimum_length": "Password must be at least 8 characters long",
"error_password_maximum_length": "Password cannot be over 64 characters long",
"error_username_in_use": "Username already in use"
},
"gameMenu":{
"history_button":"View Historical Data",
Expand Down
12 changes: 11 additions & 1 deletion webapp/src/translations/es/global.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,17 @@
"password_placeholder": "Contraseña",
"repeat_password_placeholder": "Repetir contraseña",
"register_button": "Registrarse",
"login_link": "¿Ya tienes una cuenta? Inicia sesión aquí."
"login_link": "¿Ya tienes una cuenta? Inicia sesión aquí.",
"very_weak_password": "Contraseña muy débil",
"weak_password": "Contraseña débil",
"good_password": "Contraseña buena",
"strong_password": "Contraseña fuerte",
"error_passwords_no_match": "Las contraseñas no coinciden",
"error_password_spaces": "La contraseña no puede contener espacios",
"error_username_spaces": "El nombre de usuario no puede contener espacios",
"error_password_minimum_length": "La contraseña debe tener al menos 8 caracteres",
"error_password_maximum_length": "La contraseña no debe tener más de 64 caracteres",
"error_username_in_use": "Nombre de usuario no disponible"
},
"gameMenu":{
"history_button":"Ver Historial",
Expand Down
12 changes: 11 additions & 1 deletion webapp/src/translations/tk/global.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,17 @@
"password_placeholder": "Şifre",
"repeat_password_placeholder": "Şifreyi Tekrar Girin",
"register_button": "Kayıt Ol",
"login_link": "Hesabınız var mı? Buradan giriş yapın."
"login_link": "Hesabınız var mı? Buradan giriş yapın.",
"very_weak_password": "Çok zayıf şifre",
"weak_password": "Zayıf şifre",
"good_password": "İyi şifre",
"strong_password": "Güçlü şifre",
"error_passwords_no_match": "Şifreler eşleşmiyor",
"error_password_spaces": "Şifre boşluk içeremez",
"error_username_spaces": "Kullanıcı adı boşluk içeremez.",
"error_password_minimum_length": "Şifre en az 8 karakter uzunluğunda olmalıdır",
"error_password_maximum_length": "Şifre en fazla 64 karakter uzunluğunda olabilir",
"error_username_in_use": "Kullanıcı adı zaten kullanımda"
},
"gameMenu": {
"history_button": "Tarihsel Verileri Görüntüle",
Expand Down

0 comments on commit 099084d

Please sign in to comment.