From 68179dd46e8fd8f619e3687a627ebbf1591b2591 Mon Sep 17 00:00:00 2001 From: m4 Date: Wed, 12 Aug 2026 16:45:43 +0800 Subject: [PATCH] feat(webui): add user-management validation helpers --- src/lib/userManagement.test.ts | 48 ++++++++++++++++++++++++++++++++++ src/lib/userManagement.ts | 27 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 src/lib/userManagement.test.ts create mode 100644 src/lib/userManagement.ts diff --git a/src/lib/userManagement.test.ts b/src/lib/userManagement.test.ts new file mode 100644 index 0000000..afb83d2 --- /dev/null +++ b/src/lib/userManagement.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from "vitest"; +import { + isValidPassword, + isValidUsername, + otherRole, +} from "./userManagement"; + +describe("isValidUsername", () => { + it("accepts simple names", () => { + expect(isValidUsername("alice")).toBe(true); + expect(isValidUsername("A1_b-c.d")).toBe(true); + }); + + it("rejects empty and overlong names", () => { + expect(isValidUsername("")).toBe(false); + expect(isValidUsername("a".repeat(65))).toBe(false); + }); + + it("rejects names starting with a non-alphanumeric", () => { + expect(isValidUsername("-alice")).toBe(false); + expect(isValidUsername(".alice")).toBe(false); + }); + + it("rejects illegal characters", () => { + expect(isValidUsername("al ice")).toBe(false); + expect(isValidUsername("alice!")).toBe(false); + expect(isValidUsername("爱丽丝")).toBe(false); + }); +}); + +describe("isValidPassword", () => { + it("requires at least 8 characters", () => { + expect(isValidPassword("1234567")).toBe(false); + expect(isValidPassword("12345678")).toBe(true); + }); + + it("rejects passwords over 256 characters", () => { + expect(isValidPassword("x".repeat(256))).toBe(true); + expect(isValidPassword("x".repeat(257))).toBe(false); + }); +}); + +describe("otherRole", () => { + it("flips admin and user", () => { + expect(otherRole("admin")).toBe("user"); + expect(otherRole("user")).toBe("admin"); + }); +}); diff --git a/src/lib/userManagement.ts b/src/lib/userManagement.ts new file mode 100644 index 0000000..e64325e --- /dev/null +++ b/src/lib/userManagement.ts @@ -0,0 +1,27 @@ +export const USERNAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/; +export const PASSWORD_MIN_LENGTH = 8; +export const PASSWORD_MAX_LENGTH = 256; + +export type ManagedUserRole = "admin" | "user"; + +export interface ManagedUser { + username: string; + role: ManagedUserRole; + createdAt: string; + updatedAt: string; +} + +export function isValidUsername(username: string): boolean { + return USERNAME_PATTERN.test(username); +} + +export function isValidPassword(password: string): boolean { + return ( + password.length >= PASSWORD_MIN_LENGTH && + password.length <= PASSWORD_MAX_LENGTH + ); +} + +export function otherRole(role: ManagedUserRole): ManagedUserRole { + return role === "admin" ? "user" : "admin"; +}