Spaces:
Sleeping
Sleeping
Commit
·
2ea6bf5
1
Parent(s):
92f01f5
username validation
Browse files- frontend/app/layout.js +1 -1
- frontend/app/page.js +3 -0
- frontend/components/NexusAuth.js +35 -3
frontend/app/layout.js
CHANGED
|
@@ -6,7 +6,7 @@ import { ToastContainer, Flip } from 'react-toastify';
|
|
| 6 |
import { CheckCircleIcon, InformationCircleIcon, ExclamationCircleIcon } from '@heroicons/react/20/solid';
|
| 7 |
import Sidebar from "@components/Sidebar";
|
| 8 |
|
| 9 |
-
import { ToastProvider } from "
|
| 10 |
|
| 11 |
export default function RootLayout({ children }) {
|
| 12 |
const contentStyle = {
|
|
|
|
| 6 |
import { CheckCircleIcon, InformationCircleIcon, ExclamationCircleIcon } from '@heroicons/react/20/solid';
|
| 7 |
import Sidebar from "@components/Sidebar";
|
| 8 |
|
| 9 |
+
import { ToastProvider } from "@lib/ToastContext";
|
| 10 |
|
| 11 |
export default function RootLayout({ children }) {
|
| 12 |
const contentStyle = {
|
frontend/app/page.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { useState, useEffect } from "react";
|
|
|
|
| 4 |
import NexusAuthApi from "@lib/Nexus_Auth_API";
|
| 5 |
|
| 6 |
export default function Home() {
|
|
@@ -8,6 +9,7 @@ export default function Home() {
|
|
| 8 |
const [userID, setUserID] = useState(null);
|
| 9 |
const [token, setToken] = useState(null);
|
| 10 |
const [accessLevel, setAccessLevel] = useState(null);
|
|
|
|
| 11 |
|
| 12 |
useEffect(() => {
|
| 13 |
setUsername(localStorage.getItem('me'));
|
|
@@ -28,6 +30,7 @@ export default function Home() {
|
|
| 28 |
NexusAuthApi.logout(userID, token)
|
| 29 |
.then(() => {
|
| 30 |
clearLocalStorage();
|
|
|
|
| 31 |
window.location.reload();
|
| 32 |
})
|
| 33 |
.catch((error) => {
|
|
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { useState, useEffect } from "react";
|
| 4 |
+
import { useRouter } from "next/router";
|
| 5 |
import NexusAuthApi from "@lib/Nexus_Auth_API";
|
| 6 |
|
| 7 |
export default function Home() {
|
|
|
|
| 9 |
const [userID, setUserID] = useState(null);
|
| 10 |
const [token, setToken] = useState(null);
|
| 11 |
const [accessLevel, setAccessLevel] = useState(null);
|
| 12 |
+
const router = useRouter();
|
| 13 |
|
| 14 |
useEffect(() => {
|
| 15 |
setUsername(localStorage.getItem('me'));
|
|
|
|
| 30 |
NexusAuthApi.logout(userID, token)
|
| 31 |
.then(() => {
|
| 32 |
clearLocalStorage();
|
| 33 |
+
router.push('/');
|
| 34 |
window.location.reload();
|
| 35 |
})
|
| 36 |
.catch((error) => {
|
frontend/components/NexusAuth.js
CHANGED
|
@@ -12,10 +12,14 @@ const SignupForm = ({ onSignup }) => {
|
|
| 12 |
const [confirmPassword, setConfirmPassword] = useState('');
|
| 13 |
const [email, setEmail] = useState('');
|
| 14 |
const [usernameAvailable, setUsernameAvailable] = useState(null); // null for initial state
|
|
|
|
|
|
|
| 15 |
const [passwordValid, setPasswordValid] = useState(false); // Initially invalid
|
| 16 |
const [formValid, setFormValid] = useState(false);
|
| 17 |
const [debounceTimeout, setDebounceTimeout] = useState(null); // Store timeout ID
|
| 18 |
|
|
|
|
|
|
|
| 19 |
const validatePassword = (password) => {
|
| 20 |
return password.length >= 8;
|
| 21 |
};
|
|
@@ -32,11 +36,29 @@ const SignupForm = ({ onSignup }) => {
|
|
| 32 |
clearTimeout(debounceTimeout);
|
| 33 |
}
|
| 34 |
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
// Set a new timeout to check availability
|
| 37 |
const newTimeout = setTimeout(async () => {
|
| 38 |
try {
|
| 39 |
-
const response = await NexusAuthApi.isUsernameAvailable(
|
| 40 |
setUsernameAvailable(response?.is_available === true);
|
| 41 |
} catch (error) {
|
| 42 |
console.error('Error checking username availability:', error);
|
|
@@ -48,6 +70,9 @@ const SignupForm = ({ onSignup }) => {
|
|
| 48 |
} else {
|
| 49 |
setUsernameAvailable(null); // Reset availability check when input is empty
|
| 50 |
}
|
|
|
|
|
|
|
|
|
|
| 51 |
};
|
| 52 |
|
| 53 |
const handlePasswordChange = (e) => {
|
|
@@ -76,7 +101,8 @@ const SignupForm = ({ onSignup }) => {
|
|
| 76 |
usernameAvailable === true &&
|
| 77 |
password === confirmPassword &&
|
| 78 |
passwordValid &&
|
| 79 |
-
username.length
|
|
|
|
| 80 |
);
|
| 81 |
}, [username, password, confirmPassword, usernameAvailable, passwordValid]);
|
| 82 |
|
|
@@ -94,6 +120,12 @@ const SignupForm = ({ onSignup }) => {
|
|
| 94 |
{usernameAvailable === true && username.length > 0 && (
|
| 95 |
<CheckCircleIcon className="h-5 w-5 text-green-500" />
|
| 96 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
{usernameAvailable === false && (
|
| 98 |
<p className="error-message text-red-500">Username is already taken</p>
|
| 99 |
)}
|
|
|
|
| 12 |
const [confirmPassword, setConfirmPassword] = useState('');
|
| 13 |
const [email, setEmail] = useState('');
|
| 14 |
const [usernameAvailable, setUsernameAvailable] = useState(null); // null for initial state
|
| 15 |
+
const [doesUsernameContainInvalidChars, setDoesUsernameContainInvalidChars] = useState(false);
|
| 16 |
+
const [doesUsernameExceedMinLength, setDoesUsernameExceedMinLength] = useState(false);
|
| 17 |
const [passwordValid, setPasswordValid] = useState(false); // Initially invalid
|
| 18 |
const [formValid, setFormValid] = useState(false);
|
| 19 |
const [debounceTimeout, setDebounceTimeout] = useState(null); // Store timeout ID
|
| 20 |
|
| 21 |
+
const minUsernameLength = 3;
|
| 22 |
+
|
| 23 |
const validatePassword = (password) => {
|
| 24 |
return password.length >= 8;
|
| 25 |
};
|
|
|
|
| 36 |
clearTimeout(debounceTimeout);
|
| 37 |
}
|
| 38 |
|
| 39 |
+
// Check for invalid characters
|
| 40 |
+
const invalidChars = /[^a-zA-Z0-9_]/g;
|
| 41 |
+
if (invalidChars.test(newUsername)) {
|
| 42 |
+
setDoesUsernameContainInvalidChars(true);
|
| 43 |
+
setTimeout(() => {
|
| 44 |
+
setDoesUsernameContainInvalidChars(false);
|
| 45 |
+
}, 2000); // Show error for 2 seconds
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
// Basic sanitization to prevent SQL injection
|
| 49 |
+
const sanitizedUsername = newUsername.replace(invalidChars, '');
|
| 50 |
+
|
| 51 |
+
if (sanitizedUsername.length < minUsernameLength) {
|
| 52 |
+
setDoesUsernameExceedMinLength(false);
|
| 53 |
+
return;
|
| 54 |
+
} else {
|
| 55 |
+
setDoesUsernameExceedMinLength(true);}
|
| 56 |
+
|
| 57 |
+
if (sanitizedUsername.trim().length > 0) {
|
| 58 |
// Set a new timeout to check availability
|
| 59 |
const newTimeout = setTimeout(async () => {
|
| 60 |
try {
|
| 61 |
+
const response = await NexusAuthApi.isUsernameAvailable(sanitizedUsername);
|
| 62 |
setUsernameAvailable(response?.is_available === true);
|
| 63 |
} catch (error) {
|
| 64 |
console.error('Error checking username availability:', error);
|
|
|
|
| 70 |
} else {
|
| 71 |
setUsernameAvailable(null); // Reset availability check when input is empty
|
| 72 |
}
|
| 73 |
+
|
| 74 |
+
// Set sanitized username
|
| 75 |
+
setUsername(sanitizedUsername);
|
| 76 |
};
|
| 77 |
|
| 78 |
const handlePasswordChange = (e) => {
|
|
|
|
| 101 |
usernameAvailable === true &&
|
| 102 |
password === confirmPassword &&
|
| 103 |
passwordValid &&
|
| 104 |
+
username.length >= minUsernameLength &&
|
| 105 |
+
!doesUsernameContainInvalidChars
|
| 106 |
);
|
| 107 |
}, [username, password, confirmPassword, usernameAvailable, passwordValid]);
|
| 108 |
|
|
|
|
| 120 |
{usernameAvailable === true && username.length > 0 && (
|
| 121 |
<CheckCircleIcon className="h-5 w-5 text-green-500" />
|
| 122 |
)}
|
| 123 |
+
{doesUsernameExceedMinLength === false && (
|
| 124 |
+
<p className="error-message text-red-500">Username must have more than {minUsernameLength} characters.</p>
|
| 125 |
+
)}
|
| 126 |
+
{doesUsernameContainInvalidChars === true && (
|
| 127 |
+
<p className="error-message text-red-500">Username cannot contain invalid characters.</p>
|
| 128 |
+
)}
|
| 129 |
{usernameAvailable === false && (
|
| 130 |
<p className="error-message text-red-500">Username is already taken</p>
|
| 131 |
)}
|