diff --git a/__tests__/userAuth.test.ts b/__tests__/userAuth.test.ts
index 3b1ef8b1..c8b032de 100644
--- a/__tests__/userAuth.test.ts
+++ b/__tests__/userAuth.test.ts
@@ -1,150 +1,206 @@
-/**
- * @jest-environment node
- */
-
-import app from '../server/server';
-import mockData from '../mockData';
-import { Sessions, Users } from '../server/models/reactypeModels';
-const request = require('supertest');
-const mongoose = require('mongoose');
-const mockNext = jest.fn(); // Mock nextFunction
-const MONGO_DB = import.meta.env.MONGO_DB_TEST;
-const { user } = mockData;
-const PORT = 8080;
-
-const num = Math.floor(Math.random() * 1000);
-
-beforeAll(async () => {
- await mongoose.connect(MONGO_DB, {
- useNewUrlParser: true,
- useUnifiedTopology: true
- });
-});
-
-afterAll(async () => {
- const result = await Users.deleteMany({
- _id: { $ne: '64f551e5b28d5292975e08c8' }
- }); //clear the users collection after tests are done except for the mockdata user account
- const result2 = await Sessions.deleteMany({
- cookieId: { $ne: '64f551e5b28d5292975e08c8' }
- });
- console.log(
- `${result.deletedCount} and ${result2.deletedCount} documents deleted.`
- );
- await mongoose.connection.close();
-});
-
-describe('User Authentication tests', () => {
- describe('initial connection test', () => {
- it('should connect to the server', async () => {
- const response = await request(app).get('/test');
- expect(response.status).toBe(200);
- expect(response.text).toBe('test request is working');
- });
- });
- describe('/signup', () => {
- describe('POST', () => {
- //testing new signup
- it('responds with status 200 and sessionId on valid new user signup', () => {
- return request(app)
- .post('/signup')
- .set('Content-Type', 'application/json')
- .send({
- username: `supertest${num}`,
- email: `test${num}@test.com`,
- password: `${num}`
- })
- .expect(200)
- .then((res) => expect(res.body.sessionId).not.toBeNull());
- });
-
- it('responds with status 400 and json string on invalid new user signup (Already taken)', () => {
- return request(app)
- .post('/signup')
- .send(user)
- .set('Accept', 'application/json')
- .expect('Content-Type', /json/)
- .expect(400)
- .then((res) => expect(typeof res.body).toBe('string'));
- });
- });
- });
-
- describe('/login', () => {
- // tests whether existing login information permits user to log in
- describe('POST', () => {
- it('responds with status 200 and json object on verified user login', () => {
- return request(app)
- .post('/login')
- .set('Accept', 'application/json')
- .send(user)
- .expect(200)
- .expect('Content-Type', /json/)
- .then((res) => expect(res.body.sessionId).toEqual(user.userId));
- });
- // if invalid username/password, should respond with status 400
- it('responds with status 400 and json string on invalid user login', () => {
- return request(app)
- .post('/login')
- .send({ username: 'wrongusername', password: 'wrongpassword' })
- .expect(400)
- .expect('Content-Type', /json/)
- .then((res) => expect(typeof res.body).toBe('string'));
- });
- it("returns the message 'No Username Input' when no username is entered", () => {
- return request(app)
- .post('/login')
- .send({
- username: '',
- password: 'Reactype123!@#',
- isFbOauth: false
- })
- .then((res) => expect(res.text).toBe('"No Username Input"'));
- });
-
- it("returns the message 'No Username Input' when no username is entered", () => {
- return request(app)
- .post('/login')
- .send({
- username: '',
- password: 'Reactype123!@#',
- isFbOauth: false
- })
- .then((res) => expect(res.text).toBe('"No Username Input"'));
- });
-
- it("returns the message 'No Password Input' when no password is entered", () => {
- return request(app)
- .post('/login')
- .send({
- username: 'reactype123',
- password: '',
- isFbOauth: false
- })
- .then((res) => expect(res.text).toBe('"No Password Input"'));
- });
-
- it("returns the message 'Invalid Username' when username does not exist", () => {
- return request(app)
- .post('/login')
- .send({
- username: 'l!b',
- password: 'test',
- isFbOauth: false
- })
- .then((res) => expect(res.text).toBe('"Invalid Username"'));
- });
- });
-
- it("returns the message 'Incorrect Password' when password does not match", () => {
- return request(app)
- .post('/login')
- .send({
- username: 'test',
- password: 'test',
- isFbOauth: false
- })
- .then((res) => expect(res.text).toBe('"Incorrect Password"'));
- });
- });
-});
+/**
+ * @jest-environment node
+ */
+
+import app from '../server/server';
+import mockData from '../mockData';
+import { Sessions, Users } from '../server/models/reactypeModels';
+const request = require('supertest');
+const mongoose = require('mongoose');
+const mockNext = jest.fn(); // Mock nextFunction
+const MONGO_DB = import.meta.env.MONGO_DB_TEST;
+const { user } = mockData;
+const PORT = 8080;
+
+const num = Math.floor(Math.random() * 1000);
+
+beforeAll(async () => {
+ await mongoose.connect(MONGO_DB, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true
+ });
+});
+
+afterAll(async () => {
+ const result = await Users.deleteMany({
+ _id: { $ne: '64f551e5b28d5292975e08c8' }
+ }); //clear the users collection after tests are done except for the mockdata user account
+ const result2 = await Sessions.deleteMany({
+ cookieId: { $ne: '64f551e5b28d5292975e08c8' }
+ });
+ console.log(
+ `${result.deletedCount} and ${result2.deletedCount} documents deleted.`
+ );
+ await mongoose.connection.close();
+});
+
+describe('User Authentication tests', () => {
+ describe('initial connection test', () => {
+ it('should connect to the server', async () => {
+ const response = await request(app).get('/test');
+ expect(response.status).toBe(200);
+ expect(response.text).toBe('test request is working');
+ });
+ });
+ describe('/signup', () => {
+ describe('POST', () => {
+ //testing new signup
+ it('responds with status 200 and sessionId on valid new user signup', () => {
+ return request(app)
+ .post('/signup')
+ .set('Content-Type', 'application/json')
+ .send({
+ username: `supertest${num}`,
+ email: `test${num}@test.com`,
+ password: `${num}`
+ })
+ .expect(200)
+ .then((res) => expect(res.body.sessionId).not.toBeNull());
+ });
+
+ it('responds with status 400 and json string on invalid new user signup (Already taken)', () => {
+ return request(app)
+ .post('/signup')
+ .send(user)
+ .set('Accept', 'application/json')
+ .expect('Content-Type', /json/)
+ .expect(400)
+ .then((res) => expect(typeof res.body).toBe('string'));
+ });
+ });
+ });
+
+ describe('/login', () => {
+ // tests whether existing login information permits user to log in
+ describe('POST', () => {
+ it('responds with status 200 and json object on verified user login', () => {
+ return request(app)
+ .post('/login')
+ .set('Accept', 'application/json')
+ .send(user)
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .then((res) => expect(res.body.sessionId).toEqual(user.userId));
+ });
+ // if invalid username/password, should respond with status 400
+ it('responds with status 400 and json string on invalid user login', () => {
+ return request(app)
+ .post('/login')
+ .send({ username: 'wrongusername', password: 'wrongpassword' })
+ .expect(400)
+ .expect('Content-Type', /json/)
+ .then((res) => expect(typeof res.body).toBe('string'));
+ });
+ it("returns the message 'No Username Input' when no username is entered", () => {
+ return request(app)
+ .post('/login')
+ .send({
+ username: '',
+ password: 'Reactype123!@#',
+ isFbOauth: false
+ })
+ .then((res) => expect(res.text).toBe('"No Username Input"'));
+ });
+
+ it("returns the message 'No Username Input' when no username is entered", () => {
+ return request(app)
+ .post('/login')
+ .send({
+ username: '',
+ password: 'Reactype123!@#',
+ isFbOauth: false
+ })
+ .then((res) => expect(res.text).toBe('"No Username Input"'));
+ });
+
+ it("returns the message 'No Password Input' when no password is entered", () => {
+ return request(app)
+ .post('/login')
+ .send({
+ username: 'reactype123',
+ password: '',
+ isFbOauth: false
+ })
+ .then((res) => expect(res.text).toBe('"No Password Input"'));
+ });
+
+ it("returns the message 'Invalid Username' when username does not exist", () => {
+ return request(app)
+ .post('/login')
+ .send({
+ username: 'l!b',
+ password: 'test',
+ isFbOauth: false
+ })
+ .then((res) => expect(res.text).toBe('"Invalid Username"'));
+ });
+ });
+
+ it("returns the message 'Incorrect Password' when password does not match", () => {
+ return request(app)
+ .post('/login')
+ .send({
+ username: 'test',
+ password: 'test',
+ isFbOauth: false
+ })
+ .then((res) => expect(res.text).toBe('"Incorrect Password"'));
+ });
+ });
+
+ describe('/updatePassword', () => {
+ describe('POST', () => {
+ //testing update password
+ const testUsername = `supertest${Date.now()}`;
+ const testPassword = `password${Date.now()}`;
+ it('responds with status 200 and json string on valid password update (Success)', () => {
+ return request(app)
+ .post('/updatePassword')
+ .set('Content-Type', 'application/json')
+ .send({
+ username: testUsername,
+ password: testPassword
+ })
+ .expect(200)
+ .then((res) => expect(res.body.message).toBe('Success')); // might need to be res.text instead of res.body.message
+ });
+
+ it('responds with status 400 and json string if no password is provided (Password is required.)', () => {
+ return request(app)
+ .post('/updatePassword')
+ .send({ username: testUsername })
+ .set('Accept', 'application/json')
+ .expect('Content-Type', /json/)
+ .expect(400)
+ .then((res) => expect(res.body.error).toBe('Password is required.'));
+ });
+
+ it('responds with status 404 and json string if user is not found (User not found.)', () => {
+ return request(app)
+ .post('/updatePassword')
+ .send({ username: 'doesntexist', password: 'fakepassword' })
+ .set('Accept', 'application/json')
+ .expect('Content-Type', /json/)
+ .expect(404)
+ .then((res) => expect(res.body.error).toBe('User not found.'));
+ });
+
+ it('responds with status 400 and json string the provided password is the same as the current password (New password must be different from the current password.)', () => {
+ return request(app)
+ .post('/updatePassword')
+ .send({
+ username: testUsername,
+ password: testPassword
+ })
+ .set('Accept', 'application/json')
+ .expect('Content-Type', /json/)
+ .expect(400)
+ .then((res) =>
+ expect(res.body.error).toBe(
+ 'New password must be different from the current password.'
+ )
+ );
+ });
+ });
+ });
+});
diff --git a/app/src/components/login/FBPassWord.tsx b/app/src/components/login/FBPassWord.tsx
index ff79fb0e..f594abee 100644
--- a/app/src/components/login/FBPassWord.tsx
+++ b/app/src/components/login/FBPassWord.tsx
@@ -1,206 +1,261 @@
-import React, { useState, MouseEvent } from 'react';
-import { LoginInt } from '../../interfaces/Interfaces';
-import {
- Link as RouteLink,
- withRouter,
- RouteComponentProps
-} from 'react-router-dom';
-import { newUserIsCreated } from '../../helperFunctions/auth';
-import Avatar from '@mui/material/Avatar';
-import Button from '@mui/material/Button';
-import CssBaseline from '@mui/material/CssBaseline';
-import TextField from '@mui/material/TextField';
-import Grid from '@mui/material/Grid';
-import Box from '@mui/material/Box';
-import Typography from '@mui/material/Typography';
-import makeStyles from '@mui/styles/makeStyles';
-import Container from '@mui/material/Container';
-
-function Copyright() {
- return (
-
- {'Copyright © ReacType '}
- {new Date().getFullYear()}
- {'.'}
-
- );
-}
-
-const useStyles = makeStyles((theme) => ({
- paper: {
- marginTop: theme.spacing(8),
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center'
- },
- avatar: {
- margin: theme.spacing(1),
- backgroundColor: '#3EC1AC'
- },
- form: {
- width: '100%', // Fix IE 11 issue.
- marginTop: theme.spacing(3)
- },
- submit: {
- margin: theme.spacing(3, 0, 2)
- },
- root: {
- '& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': {
- borderColor: '#3EC1AC'
- }
- }
-}));
-
-const SignUp: React.FC = (props) => {
- const classes = useStyles();
- const [password, setPassword] = useState('');
- const [passwordVerify, setPasswordVerify] = useState('');
-
- const [invalidPasswordMsg, setInvalidPasswordMsg] = useState('');
- const [invalidVerifyPasswordMsg, setInvalidVerifyPasswordMsg] = useState('');
-
- const [invalidPassword, setInvalidPassword] = useState(false);
- const [invalidVerifyPassword, setInvalidVerifyPassword] = useState(false);
-
- const handleChange = (e: React.ChangeEvent) => {
- let inputVal = e.target.value;
- switch (e.target.name) {
- case 'password':
- setPassword(inputVal);
- break;
- case 'passwordVerify':
- setPasswordVerify(inputVal);
- break;
- }
- };
-
- const handleSignUp = (e: React.MouseEvent) => {
- e.preventDefault();
- const email = props.location.state.email;
- // Reset Error Validation
- setInvalidPasswordMsg('');
- setInvalidVerifyPasswordMsg('');
- setInvalidPassword(false);
- setInvalidVerifyPassword(false);
-
- if (password === '') {
- setInvalidPassword(true);
- setInvalidPasswordMsg('No Password Entered');
- return;
- } else if (password.length < 8) {
- setInvalidPassword(true);
- setInvalidPasswordMsg('Minimum 8 Characters');
- return;
- } else if (
- !/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/i.test(
- password
- )
- ) {
- setInvalidPassword(true);
- setInvalidPasswordMsg('Minimum 1 Letter, Number, and Special Character');
- return;
- } else if (password !== passwordVerify) {
- setInvalidPassword(true);
- setInvalidVerifyPassword(true);
- setInvalidPasswordMsg('Verification Failed');
- setInvalidVerifyPasswordMsg('Verification Failed');
- setPasswordVerify('');
- return;
- } else {
- setInvalidPassword(false);
- }
-
- if (password !== passwordVerify) {
- setInvalidPassword(true);
- setInvalidVerifyPassword(true);
- setInvalidPasswordMsg('Verification Failed');
- setInvalidVerifyPasswordMsg('Verification Failed');
- setPasswordVerify('');
- return;
- } else {
- setInvalidVerifyPassword(false);
- }
-
- // get username and email from FB
- newUserIsCreated(email, email, password).then((userCreated) => {
- if (userCreated === 'Success') {
- props.history.push('/');
- } else {
- }
- });
- };
-
- return (
-
-
-
-
-
-
-
- Please enter in your new password
-
-
-
-
-
-
-
- );
-};
-
-export default withRouter(SignUp);
+import React, { useState, MouseEvent } from 'react';
+import { LoginInt } from '../../interfaces/Interfaces';
+import { SigninDark } from '../../../../app/src/public/styles/theme';
+import {
+ Link as RouteLink,
+ withRouter,
+ RouteComponentProps,
+ useHistory
+} from 'react-router-dom';
+import {
+ validateInputs,
+ handleChange,
+ resetErrorValidation,
+ updatePassword
+} from '../../helperFunctions/auth';
+import Avatar from '@mui/material/Avatar';
+import Button from '@mui/material/Button';
+import CssBaseline from '@mui/material/CssBaseline';
+import TextField from '@mui/material/TextField';
+import Grid from '@mui/material/Grid';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import { styled } from '@mui/material/styles';
+import Container from '@mui/material/Container';
+import AssignmentIcon from '@mui/icons-material/Assignment';
+import {
+ StyledEngineProvider,
+ Theme,
+ ThemeProvider
+} from '@mui/material/styles';
+
+declare module '@mui/styles/defaultTheme' {
+ interface DefaultTheme extends Theme {}
+}
+
+function Copyright() {
+ return (
+
+ {'Copyright © ReacType '}
+ {new Date().getFullYear()}
+ {'.'}
+
+ );
+}
+
+const StyledPaper = styled(Box)(({ theme }) => ({
+ marginTop: theme.spacing(8),
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center'
+}));
+
+const StyledAvatar = styled(Avatar)(({ theme }) => ({
+ margin: theme.spacing(1),
+ backgroundColor: 'white'
+}));
+
+const StyledForm = styled('form')(({ theme }) => ({
+ width: '100%', // Fix IE 11 issue.
+ marginTop: theme.spacing(3)
+}));
+
+const StyledButton = styled(Button)(({ theme }) => ({
+ margin: theme.spacing(3, 0, 2)
+}));
+
+const StyledTextField = styled(TextField)(({ theme }) => ({
+ '& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': {
+ borderColor: '#white'
+ }
+}));
+
+const FBPassWord: React.FC = () => {
+ const history = useHistory();
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const [passwordVerify, setPasswordVerify] = useState('');
+
+ const [invalidUserMsg, setInvalidUserMsg] = useState('');
+ const [invalidPasswordMsg, setInvalidPasswordMsg] = useState('');
+ const [invalidVerifyPasswordMsg, setInvalidVerifyPasswordMsg] = useState('');
+
+ const [invalidUser, setInvalidUser] = useState(false);
+ const [invalidPassword, setInvalidPassword] = useState(false);
+ const [invalidVerifyPassword, setInvalidVerifyPassword] = useState(false);
+
+ // define error setters to pass to resetErrorValidation function
+ const errorSetters = {
+ setInvalidUser,
+ setInvalidUserMsg,
+ setInvalidPassword,
+ setInvalidPasswordMsg,
+ setInvalidVerifyPassword,
+ setInvalidVerifyPasswordMsg
+ };
+ // define handle change setters to pass to handleChange function
+ const handleChangeSetters = {
+ setUsername,
+ setPassword,
+ setPasswordVerify
+ };
+
+ /**
+ * Handles input changes for form fields and updates the state accordingly.
+ * This function delegates to the `handleChange` function, passing the event
+ * and the `handleChangeSetters` for updating the specific state tied to the input fields.
+ * @param {React.ChangeEvent} e - The event object that triggered the change.
+ */
+ const handleInputChange = (e: React.ChangeEvent): void => {
+ handleChange(e, handleChangeSetters);
+ };
+
+ /**
+ * Handles the form submission for user password change. Prevents the default form submission behavior,
+ * resets any previous validation errors, and, if the input validation passes, attempts to update the user's password.
+ * Upon successful password update, the user is redirected to the login page. If the update fails or validation fails,
+ * appropriate error messages are displayed.
+ * @param {React.FormEvent} e - The event object that triggered the form submission,
+ * used to prevent the default form behavior.
+ * @returns {void} Nothing is returned from this function as it handles redirection or error display internally.
+ */
+ const handleUpdatePassword = async (e: React.FormEvent) => {
+ e.preventDefault();
+ resetErrorValidation(errorSetters); // Reset validation errors before a new password update attempt.
+ const isValid = validateInputs({
+ username,
+ password,
+ passwordVerify,
+ errorSetters
+ }); // Validate Inputs using Auth helper function
+ if (!isValid) {
+ console.log('Validation failed, not updating password.');
+ return;
+ }
+ try {
+ const isUpdated = await updatePassword(username, password);
+ console.log(isUpdated);
+ if (isUpdated === 'Success') {
+ history.push('/login');
+ } else {
+ console.log(
+ 'Update password failed: Unknown or unhandled error',
+ isUpdated
+ );
+ }
+ } catch (err) {
+ console.error(
+ 'Error during password updating in handleUpdatePassword:',
+ err
+ );
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ Please Enter In Your New Password
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Update Password
+
+
+
+
+
+ Already have an account?
+ Sign In
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default withRouter(FBPassWord);
diff --git a/app/src/components/login/SignIn.tsx b/app/src/components/login/SignIn.tsx
index f32d2642..ac1b83b7 100644
--- a/app/src/components/login/SignIn.tsx
+++ b/app/src/components/login/SignIn.tsx
@@ -1,392 +1,425 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { RouteComponentProps, Link as RouteLink } from 'react-router-dom';
-import { SigninDark } from '../../../../app/src/public/styles/theme';
-import {
- StyledEngineProvider,
- Theme,
- ThemeProvider
-} from '@mui/material/styles';
-import { useDispatch } from 'react-redux';
-import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
-import { LoginInt } from '../../interfaces/Interfaces';
-import serverConfig from '../../serverConfig.js';
-import makeStyles from '@mui/styles/makeStyles';
-import { sessionIsCreated } from '../../helperFunctions/auth';
-import {
- Divider,
- Box,
- Avatar,
- Button,
- Container,
- CssBaseline,
- Grid,
- TextField,
- Typography
-} from '@mui/material';
-
-const { API_BASE_URL } = serverConfig;
-
-declare module '@mui/styles/defaultTheme' {
- interface DefaultTheme extends Theme {}
-}
-
-function Copyright() {
- return (
-
- {'Copyright © ReacType '}
- {new Date().getFullYear()}
- {'.'}
-
- );
-}
-
-const useStyles = makeStyles((theme) => ({
- paper: {
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center'
- },
- avatar: {
- backgroundColor: 'white'
- },
- form: {
- width: '100%'
- },
- submit: {
- cursor: 'pointer'
- },
- root: {
- '& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': {
- borderColor: 'white'
- }
- }
-}));
-
-const SignIn: React.FC = (props) => {
- const classes = useStyles();
- const dispatch = useDispatch();
-
- const [username, setUsername] = useState('');
- const [password, setPassword] = useState('');
- const [invalidUserMsg, setInvalidUserMsg] = useState('');
- const [invalidPassMsg, setInvalidPassMsg] = useState('');
- const [invalidUser, setInvalidUser] = useState(false);
- const [invalidPass, setInvalidPass] = useState(false);
-
- useEffect(() => {
- const githubCookie = setInterval(() => {
- window.api?.setCookie();
- window.api?.getCookie((cookie) => {
- if (cookie[0]) {
- window.localStorage.setItem('ssid', cookie[0].value);
- clearInterval(githubCookie);
- props.history.push('/');
- } else if (window.localStorage.getItem('ssid')) {
- clearInterval(githubCookie);
- }
- });
- }, 2000);
- }, []);
-
- const handleChange = (e: React.ChangeEvent) => {
- let inputVal = e.target.value;
-
- switch (e.target.name) {
- case 'username':
- setUsername(inputVal);
- break;
-
- case 'password':
- setPassword(inputVal);
- break;
- }
- };
-
- const handleLogin = (e: React.MouseEvent) => {
- e.preventDefault();
- setInvalidUser(false);
- setInvalidUserMsg('');
- setInvalidPass(false);
- setInvalidPassMsg('');
- sessionIsCreated(username, password, false).then((loginStatus) => {
- if (loginStatus === 'Success') {
- props.history.push('/');
- } else {
- switch (loginStatus) {
- case 'No Username Input':
- setInvalidUser(true);
- setInvalidUserMsg(loginStatus);
- break;
-
- case 'No Password Input':
- setInvalidPass(true);
- setInvalidPassMsg(loginStatus);
- break;
-
- case 'Invalid Username':
- setInvalidUser(true);
- setInvalidUserMsg(loginStatus);
- break;
-
- case 'Incorrect Password':
- setInvalidPass(true);
- setInvalidPassMsg(loginStatus);
- break;
- }
- }
- });
- };
-
- const keyBindSignIn = useCallback((e) => {
- if (e.key === 'Enter') {
- e.preventDefault();
- document.getElementById('SignIn').click();
- }
- }, []);
-
- useEffect(() => {
- document.addEventListener('keydown', keyBindSignIn);
- return () => {
- document.removeEventListener('keydown', keyBindSignIn);
- };
- }, []);
-
- const handleLoginGuest = (
- e: React.MouseEvent
- ) => {
- e.preventDefault();
- window.localStorage.setItem('ssid', 'guest');
- props.history.push('/');
- };
-
- const handleGithubLogin = (
- e: React.MouseEvent
- ) => {
- e.preventDefault();
- window.location.assign(`${API_BASE_URL}/auth/github`);
- };
-
- const classBtn =
- 'MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-fullWidth';
-
- return (
-
-
-
-
-
-
-
-
-
- Log in
-
-
-
-
-
-
-
- OR
-
-
-
handleGithubLogin(e)}
- sx={{
- marginBottom: '1rem',
- textTransform: 'none',
- fontSize: '1rem',
- color: 'white',
- '$:hover': {
- cursor: 'pointer',
- color: 'black',
- textDecoration: 'underline'
- }
- }}
- >
-
- Sign In With Github
-
-
{
- e.preventDefault();
- window.location.assign(`${API_BASE_URL}/auth/google`);
- }}
- sx={{
- marginBottom: '1rem',
- textTransform: 'none',
- fontSize: '1rem',
- color: 'white',
- '$:hover': {
- cursor: 'pointer',
- color: 'black'
- }
- }}
- >
-
- Sign in With Google
-
-
handleLoginGuest(e)}
- sx={{
- marginBottom: '1rem',
- textTransform: 'none',
- fontSize: '1rem',
- color: 'white',
- '$:hover': {
- cursor: 'pointer',
- color: 'black'
- }
- }}
- >
-
- Continue as Guest
-
-
- Forgot password?
-
-
-
-
- Don't have an account?
- Sign Up
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default SignIn;
+import React, { useCallback, useEffect, useState } from 'react';
+import {
+ RouteComponentProps,
+ Link as RouteLink,
+ useHistory
+} from 'react-router-dom';
+import { SigninDark } from '../../../../app/src/public/styles/theme';
+import {
+ StyledEngineProvider,
+ Theme,
+ ThemeProvider
+} from '@mui/material/styles';
+import { styled } from '@mui/material/styles';
+import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
+import { LoginInt } from '../../interfaces/Interfaces';
+import serverConfig from '../../serverConfig.js';
+import makeStyles from '@mui/styles/makeStyles';
+import {
+ sessionIsCreated,
+ handleChange,
+ resetErrorValidation,
+ validateInputs
+} from '../../helperFunctions/auth';
+import {
+ Divider,
+ Box,
+ Avatar,
+ Button,
+ Container,
+ CssBaseline,
+ TextField,
+ Typography
+} from '@mui/material';
+
+const { API_BASE_URL } = serverConfig;
+
+declare module '@mui/styles/defaultTheme' {
+ interface DefaultTheme extends Theme {}
+}
+
+function Copyright() {
+ return (
+
+ {'Copyright © ReacType '}
+ {new Date().getFullYear()}
+ {'.'}
+
+ );
+}
+
+const useStyles = makeStyles((theme) => ({
+ paper: {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center'
+ },
+ avatar: {
+ backgroundColor: 'white'
+ },
+ form: {
+ width: '100%'
+ },
+ submit: {
+ cursor: 'pointer'
+ },
+ root: {
+ '& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': {
+ borderColor: 'white'
+ }
+ }
+}));
+
+const StyledForm = styled('form')(({ theme }) => ({
+ width: '100%', // Fix IE 11 issue.
+ marginTop: theme.spacing(3)
+}));
+
+const SignIn: React.FC = () => {
+ const classes = useStyles();
+ const history = useHistory();
+
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const [invalidUserMsg, setInvalidUserMsg] = useState('');
+ const [invalidPassMsg, setInvalidPasswordMsg] = useState('');
+ const [invalidUser, setInvalidUser] = useState(false);
+ const [invalidPass, setInvalidPassword] = useState(false);
+
+ /**
+ * Periodically checks for specific cookies and manages session state based on their presence.
+ * If a specific cookie is found, it stores the session ID in local storage and redirects to the home page.
+ * The check stops once the necessary cookie is found or if a session ID already exists in local storage.
+ */
+ useEffect(() => {
+ const githubCookie = setInterval(() => {
+ window.api?.setCookie();
+ window.api?.getCookie((cookie) => {
+ if (cookie[0]) {
+ window.localStorage.setItem('ssid', cookie[0].value);
+ clearInterval(githubCookie);
+ history.push('/');
+ } else if (window.localStorage.getItem('ssid')) {
+ clearInterval(githubCookie);
+ }
+ });
+ }, 2000);
+ }, []);
+
+ // define error setters to pass to resetErrorValidation function
+ const errorSetters = {
+ setInvalidUser,
+ setInvalidUserMsg,
+ setInvalidPassword,
+ setInvalidPasswordMsg
+ };
+ // define handle change setters to pass to handleChange function
+ const handleChangeSetters = {
+ setUsername,
+ setPassword
+ };
+
+ /**
+ * Handles input changes for form fields and updates the state accordingly.
+ * This function delegates to the `handleChange` function, passing the event
+ * and the `handleChangeSetters` for updating the specific state tied to the input fields.
+ * @param {React.ChangeEvent} e - The event object that triggered the change.
+ */
+ const handleInputChange = (e: React.ChangeEvent): void => {
+ handleChange(e, handleChangeSetters);
+ };
+
+ /**
+ * Handles the form submission for user login. This function prevents the default form submission behavior,
+ * resets any previous validation errors, and attempts to create a session with the provided credentials.
+ * If successful, the user is redirected to the home page. Otherwise, it updates the UI with appropriate error messages
+ * based on the error type returned from the login attempt.
+ * @param {React.FormEvent} e - The event object that triggered the form submission, typically used to prevent the default form behavior.
+ */
+ const handleLogin = async (e: React.FormEvent) => {
+ e.preventDefault(); // Prevent default form submission behavior.
+ resetErrorValidation(errorSetters); // Reset validation errors before a new login attempt.
+ const isValid = validateInputs({
+ username,
+ password,
+ errorSetters
+ }); // Validate Inputs using Auth helper function
+ if (!isValid) {
+ console.log('Validation failed, login attempt not processed.');
+ return;
+ }
+ // Attempt to create a session using the provided credentials.
+ try {
+ const loginStatus = await sessionIsCreated(username, password, false);
+ if (loginStatus === 'Success') {
+ console.log('Login successful, redirecting...');
+ history.push('/');
+ } else {
+ if (
+ [
+ 'No Username Input',
+ 'No Password Input',
+ 'Invalid Username',
+ 'Incorrect Password'
+ ].includes(loginStatus)
+ ) {
+ setInvalidUser(true);
+ setInvalidUserMsg(loginStatus);
+ } else {
+ console.error('Unhandled error during login:', loginStatus);
+ }
+ }
+ } catch (err) {
+ console.error('Error during signin in handleLogin:', err);
+ }
+ };
+
+ /**
+ * Handles the "Enter" key press to trigger a sign-in button click.
+ * This function checks if the pressed key is "Enter" and, if so, prevents the default action
+ * and programmatically clicks the sign-in button. This allows users to submit the form by pressing Enter.
+ * @param {KeyboardEvent} e - The keyboard event that triggered this handler.
+ */
+ const keyBindSignIn = useCallback((e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ document.getElementById('SignIn').click();
+ }
+ }, []);
+
+ /**
+ * Sets up and cleans up the keydown event listener for the sign-in form.
+ * This effect binds the 'Enter' key to trigger a sign-in button click across the component.
+ * It ensures that the event listener is removed when the component unmounts to prevent memory leaks
+ * and unintended behavior in other parts of the application.
+ */
+ useEffect(() => {
+ document.addEventListener('keydown', keyBindSignIn);
+ return () => {
+ document.removeEventListener('keydown', keyBindSignIn);
+ };
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+ Log in
+
+
+
+
+
+
+
+
+ OR
+
+
+
{
+ e.preventDefault();
+ window.location.assign(`${API_BASE_URL}/auth/github`);
+ }}
+ sx={{
+ marginBottom: '1rem',
+ textTransform: 'none',
+ fontSize: '1rem',
+ color: 'white',
+ '$:hover': {
+ cursor: 'pointer',
+ color: 'black',
+ textDecoration: 'underline'
+ }
+ }}
+ >
+
+ Sign In With Github
+
+
{
+ e.preventDefault();
+ window.location.assign(`${API_BASE_URL}/auth/google`);
+ }}
+ sx={{
+ marginBottom: '1rem',
+ textTransform: 'none',
+ fontSize: '1rem',
+ color: 'white',
+ '$:hover': {
+ cursor: 'pointer',
+ color: 'black'
+ }
+ }}
+ >
+
+ Sign in With Google
+
+
{
+ e.preventDefault();
+ window.localStorage.setItem('ssid', 'guest');
+ history.push('/');
+ }}
+ sx={{
+ marginBottom: '1rem',
+ textTransform: 'none',
+ fontSize: '1rem',
+ color: 'white',
+ '$:hover': {
+ cursor: 'pointer',
+ color: 'black'
+ }
+ }}
+ >
+
+ Continue as Guest
+
+
+ Forgot password?
+
+
+
+
+ Don't have an account?
+ Sign Up
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SignIn;
diff --git a/app/src/components/login/SignUp.tsx b/app/src/components/login/SignUp.tsx
index 5def1d5b..726d2664 100644
--- a/app/src/components/login/SignUp.tsx
+++ b/app/src/components/login/SignUp.tsx
@@ -2,18 +2,15 @@ import React, { useState } from 'react';
import {
RouteComponentProps,
Link as RouteLink,
- withRouter
+ withRouter,
+ useHistory
} from 'react-router-dom';
-import {
- SigninDark,
- SigninLight
-} from '../../../../app/src/public/styles/theme';
+import { SigninDark } from '../../../../app/src/public/styles/theme';
import {
StyledEngineProvider,
Theme,
ThemeProvider
} from '@mui/material/styles';
-import { useDispatch, useSelector } from 'react-redux';
import {
Box,
Avatar,
@@ -27,10 +24,15 @@ import {
import AssignmentIcon from '@mui/icons-material/Assignment';
import { LoginInt } from '../../interfaces/Interfaces';
-import { RootState } from '../../redux/store';
import makeStyles from '@mui/styles/makeStyles';
-import { newUserIsCreated } from '../../helperFunctions/auth';
+import {
+ newUserIsCreated,
+ handleChange,
+ resetErrorValidation,
+ validateInputs,
+ setErrorMessages
+} from '../../helperFunctions/auth';
declare module '@mui/styles/defaultTheme' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
@@ -66,135 +68,97 @@ const useStyles = makeStyles((theme) => ({
}
}));
-const SignUp: React.FC = (props) => {
+const SignUp: React.FC = () => {
const classes = useStyles();
- const dispatch = useDispatch();
+ const history = useHistory();
const [email, setEmail] = useState('');
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [passwordVerify, setPasswordVerify] = useState('');
const [invalidEmailMsg, setInvalidEmailMsg] = useState('');
- const [invalidUsernameMsg, setInvalidUsernameMsg] = useState('');
+ const [invalidUsernameMsg, setInvalidUserMsg] = useState('');
const [invalidPasswordMsg, setInvalidPasswordMsg] = useState('');
const [invalidVerifyPasswordMsg, setInvalidVerifyPasswordMsg] = useState('');
const [invalidEmail, setInvalidEmail] = useState(false);
- const [invalidUsername, setInvalidUsername] = useState(false);
+ const [invalidUsername, setInvalidUser] = useState(false);
const [invalidPassword, setInvalidPassword] = useState(false);
const [invalidVerifyPassword, setInvalidVerifyPassword] = useState(false);
- const handleChange = (e: React.ChangeEvent) => {
- let inputVal = e.target.value;
- switch (e.target.name) {
- case 'email':
- setEmail(inputVal);
- break;
- case 'username':
- setUsername(inputVal);
- break;
- case 'password':
- setPassword(inputVal);
- break;
- case 'passwordVerify':
- setPasswordVerify(inputVal);
- break;
- }
+ // define error setters to pass to resetErrorValidation function
+ const errorSetters = {
+ setInvalidEmail,
+ setInvalidEmailMsg,
+ setInvalidUser,
+ setInvalidUserMsg,
+ setInvalidPassword,
+ setInvalidPasswordMsg,
+ setInvalidVerifyPassword,
+ setInvalidVerifyPasswordMsg
+ };
+ // define handle change setters to pass to handleChange function
+ const handleChangeSetters = {
+ setEmail,
+ setUsername,
+ setPassword,
+ setPasswordVerify
};
- const handleSignUp = (e: React.MouseEvent) => {
- e.preventDefault();
-
- // Reset Error Validation
- setInvalidEmailMsg('');
- setInvalidUsernameMsg('');
- setInvalidPasswordMsg('');
- setInvalidVerifyPasswordMsg('');
- setInvalidEmail(false);
- setInvalidUsername(false);
- setInvalidPassword(false);
- setInvalidVerifyPassword(false);
-
- if (email === '') {
- setInvalidEmail(true);
- setInvalidEmailMsg('No Email Entered');
- return;
- } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(email)) {
- setInvalidEmail(true);
- setInvalidEmailMsg('Invalid Email Format');
- return;
- } else {
- setInvalidEmail(false);
- }
-
- if (username === '') {
- setInvalidUsername(true);
- setInvalidUsernameMsg('No Username Entered');
- return;
- } else if (!/^[\w\s-]{4,15}$/i.test(username)) {
- setInvalidUsername(true);
- setInvalidUsernameMsg('Must Be 4 - 15 Characters Long');
- return;
- } else if (!/^[\w-]+$/i.test(username)) {
- setInvalidUsername(true);
- setInvalidUsernameMsg('Cannot Contain Spaces or Special Characters');
- return;
- } else {
- setInvalidUsername(false);
- }
+ /**
+ * Handles input changes for form fields and updates the state accordingly.
+ * This function delegates to the `handleChange` function, passing the event
+ * and the `handleChangeSetters` for updating the specific state tied to the input fields.
+ * @param {React.ChangeEvent} e - The event object that triggered the change.
+ */
+ const handleInputChange = (e: React.ChangeEvent): void => {
+ handleChange(e, handleChangeSetters);
+ };
- if (password === '') {
- setInvalidPassword(true);
- setInvalidPasswordMsg('No Password Entered');
- return;
- } else if (password.length < 8) {
- setInvalidPassword(true);
- setInvalidPasswordMsg('Minimum 8 Characters');
- return;
- } else if (
- !/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/i.test(
- password
- )
- ) {
- setInvalidPassword(true);
- setInvalidPasswordMsg('Minimum 1 Letter, Number, and Special Character');
- return;
- } else if (password !== passwordVerify) {
- setInvalidPassword(true);
- setInvalidVerifyPassword(true);
- setInvalidPasswordMsg('Verification Failed');
- setInvalidVerifyPasswordMsg('Verification Failed');
- setPasswordVerify('');
- return;
- } else {
- setInvalidPassword(false);
- }
+ /**
+ * Handles the form submission for user registration. Prevents default form behavior,
+ * validates user inputs, and attempts to register a new user. Redirects to home on success,
+ * otherwise displays error messages based on the response.
+ * @param {React.MouseEvent} e - The event object that triggered the submission.
+ */
+ const handleSignUp = async (
+ e: React.MouseEvent
+ ) => {
+ e.preventDefault();
+ resetErrorValidation(errorSetters); // Reset validation errors before a new signup attempt.
+ const isValid = validateInputs({
+ email,
+ username,
+ password,
+ passwordVerify,
+ errorSetters
+ }); // Validate Inputs using Auth helper function
- if (password !== passwordVerify) {
- setInvalidPassword(true);
- setInvalidVerifyPassword(true);
- setInvalidPasswordMsg('Verification Failed');
- setInvalidVerifyPasswordMsg('Verification Failed');
- setPasswordVerify('');
+ if (!isValid) {
+ console.log('Validation failed, account not created.');
return;
- } else {
- setInvalidVerifyPassword(false);
}
-
- newUserIsCreated(username, email, password).then((userCreated) => {
+ try {
+ const userCreated = await newUserIsCreated(username, email, password);
if (userCreated === 'Success') {
- props.history.push('/');
+ console.log('Account creation successful, redirecting...');
+ history.push('/');
} else {
switch (userCreated) {
case 'Email Taken':
- setInvalidEmail(true);
- setInvalidEmailMsg('Email Taken');
+ setErrorMessages('email', 'Email Taken', errorSetters);
break;
case 'Username Taken':
- setInvalidUsername(true);
- setInvalidUsernameMsg('Username Taken');
+ setErrorMessages('username', 'Username Taken', errorSetters);
break;
+ default:
+ console.log(
+ 'Signup failed: Unknown or unhandled error',
+ userCreated
+ );
}
}
- });
+ } catch (error) {
+ console.error('Error during signup in handleSignUp:', error);
+ }
};
return (
@@ -227,15 +191,15 @@ const SignUp: React.FC = (props) => {
className={classes.root}
variant="outlined"
required
- fullWidth
id="email"
label="Email"
name="email"
autoComplete="email"
value={email}
- onChange={handleChange}
+ onChange={handleInputChange}
helperText={invalidEmailMsg}
error={invalidEmail}
+ sx={{ width: '100%' }}
/>
@@ -243,15 +207,15 @@ const SignUp: React.FC = (props) => {
className={classes.root}
variant="outlined"
required
- fullWidth
id="username"
label="Username"
name="username"
autoComplete="username"
value={username}
- onChange={handleChange}
+ onChange={handleInputChange}
helperText={invalidUsernameMsg}
error={invalidUsername}
+ sx={{ width: '100%' }}
/>
@@ -259,16 +223,16 @@ const SignUp: React.FC = (props) => {
className={classes.root}
variant="outlined"
required
- fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
value={password}
- onChange={handleChange}
+ onChange={handleInputChange}
helperText={invalidPasswordMsg}
error={invalidPassword}
+ sx={{ width: '100%' }}
/>
@@ -276,16 +240,16 @@ const SignUp: React.FC = (props) => {
className={classes.root}
variant="outlined"
required
- fullWidth
name="passwordVerify"
label="Verify Password"
type="password"
id="passwordVerify"
autoComplete="verify-password"
value={passwordVerify}
- onChange={handleChange}
+ onChange={handleInputChange}
helperText={invalidVerifyPasswordMsg}
error={invalidVerifyPassword}
+ sx={{ width: '100%' }}
/>
@@ -315,7 +279,6 @@ const SignUp: React.FC = (props) => {