diff --git a/frontend/src/components/UserMenu/AccountModal/index.jsx b/frontend/src/components/UserMenu/AccountModal/index.jsx
index cb39fdd59045d0fb2ba57ecf64edbf8f1439d789..9fac4aaebb9477691bb46167be8a72731cc4a95e 100644
--- a/frontend/src/components/UserMenu/AccountModal/index.jsx
+++ b/frontend/src/components/UserMenu/AccountModal/index.jsx
@@ -7,6 +7,7 @@ import { Plus, X } from "@phosphor-icons/react";
 
 export default function AccountModal({ user, hideModal }) {
   const { pfp, setPfp } = usePfp();
+
   const handleFileUpload = async (event) => {
     const file = event.target.files[0];
     if (!file) return false;
@@ -133,6 +134,10 @@ export default function AccountModal({ user, hideModal }) {
                 required
                 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>
               <label
@@ -143,10 +148,14 @@ export default function AccountModal({ user, hideModal }) {
               </label>
               <input
                 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"
                 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>
             <LanguagePreference />
           </div>
diff --git a/frontend/src/pages/Admin/Users/NewUserModal/index.jsx b/frontend/src/pages/Admin/Users/NewUserModal/index.jsx
index c784228b17fa9d8327107b57e50269b4a17e5918..3af7ebd4a26adcece544ed3696d961037227a021 100644
--- a/frontend/src/pages/Admin/Users/NewUserModal/index.jsx
+++ b/frontend/src/pages/Admin/Users/NewUserModal/index.jsx
@@ -7,6 +7,7 @@ import { RoleHintDisplay } from "..";
 export default function NewUserModal({ closeModal }) {
   const [error, setError] = useState(null);
   const [role, setRole] = useState("default");
+
   const handleCreate = async (e) => {
     setError(null);
     e.preventDefault();
@@ -54,7 +55,18 @@ export default function NewUserModal({ closeModal }) {
                   minLength={2}
                   required={true}
                   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>
                 <label
@@ -70,7 +82,11 @@ export default function NewUserModal({ closeModal }) {
                   placeholder="User's initial password"
                   required={true}
                   autoComplete="off"
+                  minLength={8}
                 />
+                <p className="mt-2 text-xs text-white/60">
+                  Password must be at least 8 characters long
+                </p>
               </div>
               <div>
                 <label
@@ -84,10 +100,10 @@ export default function NewUserModal({ closeModal }) {
                   required={true}
                   defaultValue={"default"}
                   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="manager">Manager </option>
+                  <option value="manager">Manager</option>
                   {user?.role === "admin" && (
                     <option value="admin">Administrator</option>
                   )}
diff --git a/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx b/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx
index 313f81f23415070585b9b3ee797f7ecad5c9ae97..ec234c2f4130374adce2cc04f466b996bc489684 100644
--- a/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx
+++ b/frontend/src/pages/Admin/Users/UserRow/EditUserModal/index.jsx
@@ -52,11 +52,15 @@ export default function EditUserModal({ currentUser, user, closeModal }) {
                   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"
                   placeholder="User's username"
-                  minLength={2}
                   defaultValue={user.username}
+                  minLength={2}
                   required={true}
                   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>
                 <label
@@ -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"
                   placeholder={`${user.username}'s new password`}
                   autoComplete="off"
+                  minLength={8}
                 />
+                <p className="mt-2 text-xs text-white/60">
+                  Password must be at least 8 characters long
+                </p>
               </div>
               <div>
                 <label
@@ -85,7 +93,7 @@ export default function EditUserModal({ currentUser, user, closeModal }) {
                   required={true}
                   defaultValue={user.role}
                   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="manager">Manager</option>
diff --git a/server/models/user.js b/server/models/user.js
index 4b14bb58f9e18428d51c5314f4db1ee96ef673a6..a149a45ea6ae5f4e3952a05b338e9d35a90b5e8c 100644
--- a/server/models/user.js
+++ b/server/models/user.js
@@ -2,6 +2,7 @@ const prisma = require("../utils/prisma");
 const { EventLogs } = require("./eventLogs");
 
 const User = {
+  usernameRegex: new RegExp(/^[a-z0-9_-]+$/),
   writable: [
     // Used for generic updates so we can validate keys in request body
     "username",
@@ -32,7 +33,6 @@ const User = {
       return String(role);
     },
   },
-
   // validations for the above writable fields.
   castColumnValue: function (key, value) {
     switch (key) {
@@ -55,6 +55,12 @@ const User = {
     }
 
     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 hashedPassword = bcrypt.hashSync(password, 10);
       const user = await prisma.users.create({
@@ -70,7 +76,6 @@ const User = {
       return { user: null, error: error.message };
     }
   },
-
   // Log the changes to a user object, but omit sensitive fields
   // that are not meant to be logged.
   loggedChanges: function (updates, prev = {}) {
@@ -93,7 +98,6 @@ const User = {
         where: { id: parseInt(userId) },
       });
       if (!currentUser) return { success: false, error: "User not found" };
-
       // Removes non-writable fields for generic updates
       // and force-casts to the proper type;
       Object.entries(updates).forEach(([key, value]) => {
@@ -123,6 +127,17 @@ const User = {
         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({
         where: { id: parseInt(userId) },
         data: updates,
@@ -170,7 +185,6 @@ const User = {
       return null;
     }
   },
-
   // Returns user object with all fields
   _get: async function (clause = {}) {
     try {