From f71e96efe247fa9fc6d7611bdf5fa1e6c689f82a Mon Sep 17 00:00:00 2001 From: Kieran Gordon Date: Tue, 17 Dec 2024 20:36:23 +0000 Subject: [PATCH] Refactor configuration handling and enhance password requirements component; update Nginx configuration and remove deprecated files --- .gitignore | 1 - client/src/components/FooterComponent.tsx | 9 +- .../PasswordRequirementsComponent.tsx | 51 +++ .../src/screens/LoginRegistrationScreen.tsx | 312 ++++++++++-------- client/src/screens/UserManagementScreen.tsx | 86 ++--- client/nginx/example.conf => nginx/nginx.conf | 2 +- server/app.py | 4 +- server/config.py | 8 +- server/gunicorn.conf.py | 12 +- server/models.py | 6 +- server/routes/config_endpoints.py | 5 + server/routes/vm_endpoints.py | 227 +++++++------ 12 files changed, 409 insertions(+), 314 deletions(-) create mode 100644 client/src/components/PasswordRequirementsComponent.tsx rename client/nginx/example.conf => nginx/nginx.conf (97%) diff --git a/.gitignore b/.gitignore index 0d0e4ff..902db39 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ instance/ iso/ logs/ migrations/ -nginx.conf node_modules/ npm-debug.log* .pnp diff --git a/client/src/components/FooterComponent.tsx b/client/src/components/FooterComponent.tsx index f56fcc2..170fa6e 100644 --- a/client/src/components/FooterComponent.tsx +++ b/client/src/components/FooterComponent.tsx @@ -50,20 +50,13 @@ const FooterComponent: FC = (): ReactElement => { .

- Originally developed as part of a dissertation project for{" "} + Initially created for a dissertation project at{" "} Heriot-Watt University's School of Mathematical and Computer Sciences .

-

- Logos courtesy of{" "} - - Wikimedia Commons - - . Respective owners retain all rights, unless otherwise stated. -

diff --git a/client/src/components/PasswordRequirementsComponent.tsx b/client/src/components/PasswordRequirementsComponent.tsx new file mode 100644 index 0000000..2cd1fe9 --- /dev/null +++ b/client/src/components/PasswordRequirementsComponent.tsx @@ -0,0 +1,51 @@ +/* + * PasswordRequirementsComponent.tsx - Display password requirements for a password input. + * Copyright (C) 2024, Kieran Gordon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React from 'react'; + +interface PasswordRequirementsProps { + password: string; +} + +const PasswordRequirementsComponent: React.FC = ({ password }) => { + const requirements = [ + { regex: /.{8,}/, label: 'At least 8 characters' }, + { regex: /[A-Z]/, label: 'At least 1 uppercase letter' }, + { regex: /[a-z]/, label: 'At least 1 lowercase letter' }, + { regex: /[0-9].*[0-9]/, label: 'At least 2 digits' }, + { regex: /[!@#$%^&*(),.?":{}|<>]/, label: 'At least 1 symbol' }, + { regex: /^\S*$/, label: 'No spaces' }, + ]; + + return ( +
    + {requirements.map((requirement, index) => ( +
  • + {requirement.label} +
  • + ))} +
+ ); +}; + +export default PasswordRequirementsComponent; \ No newline at end of file diff --git a/client/src/screens/LoginRegistrationScreen.tsx b/client/src/screens/LoginRegistrationScreen.tsx index 45bf393..e718e1f 100644 --- a/client/src/screens/LoginRegistrationScreen.tsx +++ b/client/src/screens/LoginRegistrationScreen.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import passwordValidator from "password-validator"; +import "bootstrap-icons/font/bootstrap-icons.css"; import { FC, ReactElement, useEffect, useState } from "react"; import { Alert, @@ -27,6 +27,7 @@ import { Form, Modal, Row, + Card, } from "react-bootstrap"; import { useNavigate } from "react-router-dom"; import validator from "validator"; @@ -39,6 +40,7 @@ import { } from "../api/AccountsAPI"; import Footer from "../components/FooterComponent"; import NavbarComponent from "../components/NavbarComponent"; +import PasswordRequirementsComponent from "../components/PasswordRequirementsComponent"; const LoginRegistrationScreen: FC = (): ReactElement => { const [loginUsername, setLoginUsername] = useState(""); @@ -46,6 +48,8 @@ const LoginRegistrationScreen: FC = (): ReactElement => { const [registerUsername, setRegisterUsername] = useState(""); const [registerEmail, setRegisterEmail] = useState(""); const [registerPassword, setRegisterPassword] = useState(""); + const [registerPasswordMatch, setRegisterPasswordMatch] = useState(""); + const [showPassword, setShowPassword] = useState(false); const [errorMessage, setErrorMessage] = useState(""); const [showModal, setShowModal] = useState(false); const [token, setToken] = useState(""); @@ -53,7 +57,6 @@ const LoginRegistrationScreen: FC = (): ReactElement => { const [twoFactorMessage, setTwoFactorMessage] = useState(""); // Error message for 2FA const [twoFactorCode, setTwoFactorCode] = useState(""); const [emailMessage, setEmailMessage] = useState(""); // Error message for email verification - const schema = new passwordValidator(); const navigate = useNavigate(); useEffect(() => { @@ -114,6 +117,11 @@ const LoginRegistrationScreen: FC = (): ReactElement => { return; } + if (registerPassword !== registerPasswordMatch) { + setErrorMessage("Passwords do not match."); + return; + } + // Username can only contain letters, numbers, underscores, and dashes. It cannot contain spaces. if (!validator.matches(registerUsername, /^[a-zA-Z0-9_-]+$/)) { setErrorMessage( @@ -123,26 +131,11 @@ const LoginRegistrationScreen: FC = (): ReactElement => { } // Minimum length 8, maximum length 100, must have uppercase, must have lowercase, must have 2 digits, must not have spaces - schema - .is() - .min(8) - .is() - .max(100) - .has() - .uppercase() - .has() - .lowercase() - .has() - .digits(2) - .has() - .not() - .spaces() - .has() - .symbols(); - - if (!schema.validate(registerPassword)) { + if ( + !validator.matches(registerPassword, /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d.*\d)(?!.*\s).{8,100}$/) + ) { setErrorMessage( - "Invalid password. Your password must be at least 8 characters long, have at least 1 uppercase letter, have at least 1 lowercase letter, have 1 symbol, have at least 2 digits, and must not have spaces." + "Invalid password. Your password must be at least 8 characters long, contain at least 1 uppercase letter, contain at least 1 lowercase letter, contain at least 2 digits, and not contain spaces." ); return; } @@ -204,132 +197,150 @@ const LoginRegistrationScreen: FC = (): ReactElement => { }; return ( -
+
-

Login

-

Already have an account? Login.

- - {errorMessage} - -
{ - e.preventDefault(); - LoginButton(); - }} - > - - - setLoginUsername(e.target.value)} - /> - - - - - setLoginPassword(e.target.value)} - /> - - - - - - - -
+ + +

Login

+

Login to your account.

+
{ + e.preventDefault(); + LoginButton(); + }} + > + + + setLoginUsername(e.target.value)} + /> + + + + + setLoginPassword(e.target.value)} + /> + + + + + + + + + + +
+
+
- + -

Register

-

New to Buffet? Register for an account.

- - Rules for usernames and passwords -
-
    -
  • - Usernames can only contain letters, numbers, underscores, and - dashes. It cannot contain spaces. -
  • -
  • - Passwords must be at least 8 characters long, have at least 1 - uppercase letter, have at least 1 lowercase letter, have 1 - symbol, have at least 2 digits, and must not have spaces. -
  • -
-
-
{ - e.preventDefault(); - RegisterButton(); - }} - > - - - setRegisterUsername(e.target.value)} - /> - - - - - setRegisterEmail(e.target.value)} - /> - - - - - setRegisterPassword(e.target.value)} - /> - - - - - - - -
+ + +

Register

+

Register for an account.

+
{ + e.preventDefault(); + RegisterButton(); + }} + > + + + setRegisterUsername(e.target.value)} + /> + + + + + setRegisterEmail(e.target.value)} + /> + + + + + setRegisterPassword(e.target.value)} + /> + + + + + setRegisterPasswordMatch(e.target.value)} + /> + + + + + + + + + + + + +
+
@@ -446,7 +457,26 @@ const LoginRegistrationScreen: FC = (): ReactElement => { -
+ + {/* Error message modal */} + setErrorMessage("")} + centered + > + + Error + + + {errorMessage} + + + + + +
); }; diff --git a/client/src/screens/UserManagementScreen.tsx b/client/src/screens/UserManagementScreen.tsx index 80623df..ca63d0f 100644 --- a/client/src/screens/UserManagementScreen.tsx +++ b/client/src/screens/UserManagementScreen.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import passwordValidator from "password-validator"; +import "bootstrap-icons/font/bootstrap-icons.css"; import { FC, ReactElement, useContext, useEffect, useState } from "react"; import { Alert, @@ -44,12 +44,12 @@ import { import Footer from "../components/FooterComponent"; import NavbarComponent from "../components/NavbarComponent"; import { AuthContext } from "../contexts/AuthContext"; +import PasswordRequirementsComponent from "../components/PasswordRequirementsComponent"; const UserManagementScreen: FC = (): ReactElement => { const authContext = useContext(AuthContext); const user = authContext?.user; const navigate = useNavigate(); - const schema = new passwordValidator(); const [getEmail, setCurrentEmail] = useState(""); const [getUserName, setCurrentUserName] = useState(""); @@ -62,6 +62,7 @@ const UserManagementScreen: FC = (): ReactElement => { const [warningMessage, setWarningMessage] = useState(""); const [showDeleteModal, setShowDeleteModal] = useState(false); const [showTwoFactorModal, setShowTwoFactorModal] = useState(false); + const [showPassword, setShowPassword] = useState(false); const [qrCode, setQrCode] = useState(""); const [twoFactorCode, setTwoFactorCode] = useState(""); const [showDisableTwoFactorModal, setShowDisableTwoFactorModal] = useState(false); @@ -114,24 +115,11 @@ const UserManagementScreen: FC = (): ReactElement => { } // Minimum length 8, maximum length 100, must have uppercase, must have lowercase, must have 2 digits, must not have spaces - schema - .is() - .min(8) - .max(100) - .has() - .uppercase() - .has() - .lowercase() - .has() - .digits(2) - .has() - .symbols() - .not() - .spaces(); - - if (!schema.validate(newPassword)) { + if ( + !validator.matches(newPassword, /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d.*\d)(?!.*\s).{8,100}$/) + ) { setWarningMessage( - "Invalid password. Your password must be at least 8 characters long, have at least 1 uppercase letter, have at least 1 lowercase letter, have 1 symbol, have at least 2 digits, and must not have spaces." + "Invalid password. Your password must be at least 8 characters long, contain at least one uppercase letter, contain at least one lowercase letter, contain at least two digits, and not contain any spaces." ); return; } @@ -342,7 +330,7 @@ const UserManagementScreen: FC = (): ReactElement => { setCurrentPassword(e.target.value)} /> @@ -379,9 +367,14 @@ const UserManagementScreen: FC = (): ReactElement => { )} - + + + + @@ -409,7 +402,7 @@ const UserManagementScreen: FC = (): ReactElement => { setCurrentPassword(e.target.value)} /> @@ -446,9 +439,14 @@ const UserManagementScreen: FC = (): ReactElement => { )} - + + + + @@ -458,6 +456,7 @@ const UserManagementScreen: FC = (): ReactElement => { Change Password +
{ e.preventDefault(); @@ -467,7 +466,7 @@ const UserManagementScreen: FC = (): ReactElement => { setOldPassword(e.target.value)} /> @@ -476,7 +475,7 @@ const UserManagementScreen: FC = (): ReactElement => { setNewPassword(e.target.value)} /> @@ -513,9 +512,14 @@ const UserManagementScreen: FC = (): ReactElement => { )} - + + + + @@ -536,7 +540,7 @@ const UserManagementScreen: FC = (): ReactElement => { setCurrentPassword(e.target.value)} /> @@ -573,9 +577,14 @@ const UserManagementScreen: FC = (): ReactElement => { )} - + + + + @@ -651,7 +660,7 @@ const UserManagementScreen: FC = (): ReactElement => { setCurrentPassword(e.target.value)} /> @@ -681,6 +690,9 @@ const UserManagementScreen: FC = (): ReactElement => { +