From fef77eea614c92037fa47f96e3b5d85692f036d6 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 5 Dec 2025 14:03:09 +0100 Subject: [PATCH] feat: add minLength to pw strategy, and enforce --- .../auth/strategies/PasswordStrategy.spec.ts | 13 +++++ app/src/auth/api/AuthController.ts | 48 +++++++++++-------- .../strategies/PasswordStrategy.ts | 16 +++++-- app/src/ui/elements/auth/AuthForm.tsx | 2 +- 4 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 app/__test__/auth/strategies/PasswordStrategy.spec.ts diff --git a/app/__test__/auth/strategies/PasswordStrategy.spec.ts b/app/__test__/auth/strategies/PasswordStrategy.spec.ts new file mode 100644 index 0000000..7b290a4 --- /dev/null +++ b/app/__test__/auth/strategies/PasswordStrategy.spec.ts @@ -0,0 +1,13 @@ +import { PasswordStrategy } from "auth/authenticate/strategies/PasswordStrategy"; +import { describe, expect, it } from "bun:test"; + +describe("PasswordStrategy", () => { + it("should enforce provided minimum length", async () => { + const strategy = new PasswordStrategy({ minLength: 8, hashing: "plain" }); + + expect(strategy.verify("password")({} as any)).rejects.toThrow(); + expect( + strategy.verify("password1234")({ strategy_value: "password1234" } as any), + ).resolves.toBeUndefined(); + }); +}); diff --git a/app/src/auth/api/AuthController.ts b/app/src/auth/api/AuthController.ts index 99f1000..af846ce 100644 --- a/app/src/auth/api/AuthController.ts +++ b/app/src/auth/api/AuthController.ts @@ -13,6 +13,7 @@ import { InvalidSchemaError, transformObject, mcpTool, + $console, } from "bknd/utils"; import type { PasswordStrategy } from "auth/authenticate/strategies"; @@ -210,7 +211,7 @@ export class AuthController extends Controller { const idType = s.anyOf([s.number({ title: "Integer" }), s.string({ title: "UUID" })]); const getUser = async (params: { id?: string | number; email?: string }) => { - let user: DB["users"] | undefined = undefined; + let user: DB["users"] | undefined; if (params.id) { const { data } = await this.userRepo.findId(params.id); user = data; @@ -225,26 +226,33 @@ export class AuthController extends Controller { }; const roles = Object.keys(this.auth.config.roles ?? {}); - mcp.tool( - "auth_user_create", - { - description: "Create a new user", - inputSchema: s.object({ - email: s.string({ format: "email" }), - password: s.string({ minLength: 8 }), - role: s - .string({ - enum: roles.length > 0 ? roles : undefined, - }) - .optional(), - }), - }, - async (params, c) => { - await c.context.ctx().helper.granted(c, AuthPermissions.createUser); + try { + const actions = this.auth.authenticator.strategy("password").getActions(); + if (actions.create) { + const schema = actions.create.schema; + mcp.tool( + "auth_user_create", + { + description: "Create a new user", + inputSchema: s.object({ + ...schema.properties, + role: s + .string({ + enum: roles.length > 0 ? roles : undefined, + }) + .optional(), + }), + }, + async (params, c) => { + await c.context.ctx().helper.granted(c, AuthPermissions.createUser); - return c.json(await this.auth.createUser(params)); - }, - ); + return c.json(await this.auth.createUser(params)); + }, + ); + } + } catch (e) { + $console.warn("error creating auth_user_create tool", e); + } mcp.tool( "auth_user_token", diff --git a/app/src/auth/authenticate/strategies/PasswordStrategy.ts b/app/src/auth/authenticate/strategies/PasswordStrategy.ts index 0c6066b..7f2c964 100644 --- a/app/src/auth/authenticate/strategies/PasswordStrategy.ts +++ b/app/src/auth/authenticate/strategies/PasswordStrategy.ts @@ -10,6 +10,7 @@ const schema = s .object({ hashing: s.string({ enum: ["plain", "sha256", "bcrypt"], default: "sha256" }), rounds: s.number({ minimum: 1, maximum: 10 }).optional(), + minLength: s.number({ default: 8, minimum: 1 }).optional(), }) .strict(); @@ -37,7 +38,7 @@ export class PasswordStrategy extends AuthStrategy { format: "email", }), password: s.string({ - minLength: 8, // @todo: this should be configurable + minLength: this.config.minLength, }), }); } @@ -65,12 +66,21 @@ export class PasswordStrategy extends AuthStrategy { return await bcryptCompare(compare, actual); } - return false; + return actual === compare; } verify(password: string) { return async (user: User) => { - const compare = await this.compare(user?.strategy_value!, password); + if (!user || !user.strategy_value) { + throw new InvalidCredentialsException(); + } + + if (!this.getPayloadSchema().properties.password.validate(password).valid) { + $console.debug("PasswordStrategy: Invalid password", password); + throw new InvalidCredentialsException(); + } + + const compare = await this.compare(user.strategy_value, password); if (compare !== true) { throw new InvalidCredentialsException(); } diff --git a/app/src/ui/elements/auth/AuthForm.tsx b/app/src/ui/elements/auth/AuthForm.tsx index d412b8e..edf837e 100644 --- a/app/src/ui/elements/auth/AuthForm.tsx +++ b/app/src/ui/elements/auth/AuthForm.tsx @@ -103,7 +103,7 @@ export function AuthForm({ - +