Skip to content
Snippets Groups Projects
Unverified Commit c9706652 authored by Sean Hatfield's avatar Sean Hatfield Committed by GitHub
Browse files

New user account validations (#2037)


* force lowercase and no space for new and
 edit user modals

* edit account modal validations

* use pattern for form validation + remove validations from edit user

* revert comment deletions

* comment fix

* update validation message

* update regex
allow updating by block name changes to invalid names

---------

Co-authored-by: default avatartimothycarambat <rambat1010@gmail.com>
parent d072875e
No related branches found
No related tags found
No related merge requests found
...@@ -7,6 +7,7 @@ import { Plus, X } from "@phosphor-icons/react"; ...@@ -7,6 +7,7 @@ import { Plus, X } from "@phosphor-icons/react";
export default function AccountModal({ user, hideModal }) { export default function AccountModal({ user, hideModal }) {
const { pfp, setPfp } = usePfp(); const { pfp, setPfp } = usePfp();
const handleFileUpload = async (event) => { const handleFileUpload = async (event) => {
const file = event.target.files[0]; const file = event.target.files[0];
if (!file) return false; if (!file) return false;
...@@ -133,6 +134,10 @@ export default function AccountModal({ user, hideModal }) { ...@@ -133,6 +134,10 @@ export default function AccountModal({ user, hideModal }) {
required required
autoComplete="off" autoComplete="off"
/> />
<p className="mt-2 text-xs text-white/60">
Username must be only contain lowercase letters, numbers,
underscores, and hyphens with no spaces
</p>
</div> </div>
<div> <div>
<label <label
...@@ -143,10 +148,14 @@ export default function AccountModal({ user, hideModal }) { ...@@ -143,10 +148,14 @@ export default function AccountModal({ user, hideModal }) {
</label> </label>
<input <input
name="password" name="password"
type="password" type="text"
className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder={`${user.username}'s new password`} placeholder={`${user.username}'s new password`}
minLength={8}
/> />
<p className="mt-2 text-xs text-white/60">
Password must be at least 8 characters long
</p>
</div> </div>
<LanguagePreference /> <LanguagePreference />
</div> </div>
......
...@@ -7,6 +7,7 @@ import { RoleHintDisplay } from ".."; ...@@ -7,6 +7,7 @@ import { RoleHintDisplay } from "..";
export default function NewUserModal({ closeModal }) { export default function NewUserModal({ closeModal }) {
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [role, setRole] = useState("default"); const [role, setRole] = useState("default");
const handleCreate = async (e) => { const handleCreate = async (e) => {
setError(null); setError(null);
e.preventDefault(); e.preventDefault();
...@@ -54,7 +55,18 @@ export default function NewUserModal({ closeModal }) { ...@@ -54,7 +55,18 @@ export default function NewUserModal({ closeModal }) {
minLength={2} minLength={2}
required={true} required={true}
autoComplete="off" autoComplete="off"
pattern="^[a-z0-9_-]+$"
onInvalid={(e) =>
e.target.setCustomValidity(
"Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces"
)
}
onChange={(e) => e.target.setCustomValidity("")}
/> />
<p className="mt-2 text-xs text-white/60">
Username must be only contain lowercase letters, numbers,
underscores, and hyphens with no spaces
</p>
</div> </div>
<div> <div>
<label <label
...@@ -70,7 +82,11 @@ export default function NewUserModal({ closeModal }) { ...@@ -70,7 +82,11 @@ export default function NewUserModal({ closeModal }) {
placeholder="User's initial password" placeholder="User's initial password"
required={true} required={true}
autoComplete="off" autoComplete="off"
minLength={8}
/> />
<p className="mt-2 text-xs text-white/60">
Password must be at least 8 characters long
</p>
</div> </div>
<div> <div>
<label <label
...@@ -84,10 +100,10 @@ export default function NewUserModal({ closeModal }) { ...@@ -84,10 +100,10 @@ export default function NewUserModal({ closeModal }) {
required={true} required={true}
defaultValue={"default"} defaultValue={"default"}
onChange={(e) => setRole(e.target.value)} onChange={(e) => setRole(e.target.value)}
className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border-gray-500 focus:ring-blue-500 focus:border-blue-500" className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border-gray-500 focus:ring-blue-500 focus:border-blue-500 w-full"
> >
<option value="default">Default</option> <option value="default">Default</option>
<option value="manager">Manager </option> <option value="manager">Manager</option>
{user?.role === "admin" && ( {user?.role === "admin" && (
<option value="admin">Administrator</option> <option value="admin">Administrator</option>
)} )}
......
...@@ -52,11 +52,15 @@ export default function EditUserModal({ currentUser, user, closeModal }) { ...@@ -52,11 +52,15 @@ export default function EditUserModal({ currentUser, user, closeModal }) {
type="text" type="text"
className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="User's username" placeholder="User's username"
minLength={2}
defaultValue={user.username} defaultValue={user.username}
minLength={2}
required={true} required={true}
autoComplete="off" autoComplete="off"
/> />
<p className="mt-2 text-xs text-white/60">
Username must be only contain lowercase letters, numbers,
underscores, and hyphens with no spaces
</p>
</div> </div>
<div> <div>
<label <label
...@@ -71,7 +75,11 @@ export default function EditUserModal({ currentUser, user, closeModal }) { ...@@ -71,7 +75,11 @@ export default function EditUserModal({ currentUser, user, closeModal }) {
className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" className="bg-zinc-900 placeholder:text-white/20 border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder={`${user.username}'s new password`} placeholder={`${user.username}'s new password`}
autoComplete="off" autoComplete="off"
minLength={8}
/> />
<p className="mt-2 text-xs text-white/60">
Password must be at least 8 characters long
</p>
</div> </div>
<div> <div>
<label <label
...@@ -85,7 +93,7 @@ export default function EditUserModal({ currentUser, user, closeModal }) { ...@@ -85,7 +93,7 @@ export default function EditUserModal({ currentUser, user, closeModal }) {
required={true} required={true}
defaultValue={user.role} defaultValue={user.role}
onChange={(e) => setRole(e.target.value)} onChange={(e) => setRole(e.target.value)}
className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border-gray-500 focus:ring-blue-500 focus:border-blue-500" className="rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border-gray-500 focus:ring-blue-500 focus:border-blue-500 w-full"
> >
<option value="default">Default</option> <option value="default">Default</option>
<option value="manager">Manager</option> <option value="manager">Manager</option>
......
...@@ -2,6 +2,7 @@ const prisma = require("../utils/prisma"); ...@@ -2,6 +2,7 @@ const prisma = require("../utils/prisma");
const { EventLogs } = require("./eventLogs"); const { EventLogs } = require("./eventLogs");
const User = { const User = {
usernameRegex: new RegExp(/^[a-z0-9_-]+$/),
writable: [ writable: [
// Used for generic updates so we can validate keys in request body // Used for generic updates so we can validate keys in request body
"username", "username",
...@@ -32,7 +33,6 @@ const User = { ...@@ -32,7 +33,6 @@ const User = {
return String(role); return String(role);
}, },
}, },
// validations for the above writable fields. // validations for the above writable fields.
castColumnValue: function (key, value) { castColumnValue: function (key, value) {
switch (key) { switch (key) {
...@@ -55,6 +55,12 @@ const User = { ...@@ -55,6 +55,12 @@ const User = {
} }
try { try {
// Do not allow new users to bypass validation
if (!this.usernameRegex.test(username))
throw new Error(
"Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces"
);
const bcrypt = require("bcrypt"); const bcrypt = require("bcrypt");
const hashedPassword = bcrypt.hashSync(password, 10); const hashedPassword = bcrypt.hashSync(password, 10);
const user = await prisma.users.create({ const user = await prisma.users.create({
...@@ -70,7 +76,6 @@ const User = { ...@@ -70,7 +76,6 @@ const User = {
return { user: null, error: error.message }; return { user: null, error: error.message };
} }
}, },
// Log the changes to a user object, but omit sensitive fields // Log the changes to a user object, but omit sensitive fields
// that are not meant to be logged. // that are not meant to be logged.
loggedChanges: function (updates, prev = {}) { loggedChanges: function (updates, prev = {}) {
...@@ -93,7 +98,6 @@ const User = { ...@@ -93,7 +98,6 @@ const User = {
where: { id: parseInt(userId) }, where: { id: parseInt(userId) },
}); });
if (!currentUser) return { success: false, error: "User not found" }; if (!currentUser) return { success: false, error: "User not found" };
// Removes non-writable fields for generic updates // Removes non-writable fields for generic updates
// and force-casts to the proper type; // and force-casts to the proper type;
Object.entries(updates).forEach(([key, value]) => { Object.entries(updates).forEach(([key, value]) => {
...@@ -123,6 +127,17 @@ const User = { ...@@ -123,6 +127,17 @@ const User = {
updates.password = bcrypt.hashSync(updates.password, 10); updates.password = bcrypt.hashSync(updates.password, 10);
} }
if (
updates.hasOwnProperty("username") &&
currentUser.username !== updates.username &&
!this.usernameRegex.test(updates.username)
)
return {
success: false,
error:
"Username must be only contain lowercase letters, numbers, underscores, and hyphens with no spaces",
};
const user = await prisma.users.update({ const user = await prisma.users.update({
where: { id: parseInt(userId) }, where: { id: parseInt(userId) },
data: updates, data: updates,
...@@ -170,7 +185,6 @@ const User = { ...@@ -170,7 +185,6 @@ const User = {
return null; return null;
} }
}, },
// Returns user object with all fields // Returns user object with all fields
_get: async function (clause = {}) { _get: async function (clause = {}) {
try { try {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment