feat: add minLength to pw strategy, and enforce

This commit is contained in:
dswbx
2025-12-05 14:03:09 +01:00
parent ed41887d74
commit fef77eea61
4 changed files with 55 additions and 24 deletions

View File

@@ -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();
});
});

View File

@@ -13,6 +13,7 @@ import {
InvalidSchemaError, InvalidSchemaError,
transformObject, transformObject,
mcpTool, mcpTool,
$console,
} from "bknd/utils"; } from "bknd/utils";
import type { PasswordStrategy } from "auth/authenticate/strategies"; 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 idType = s.anyOf([s.number({ title: "Integer" }), s.string({ title: "UUID" })]);
const getUser = async (params: { id?: string | number; email?: string }) => { const getUser = async (params: { id?: string | number; email?: string }) => {
let user: DB["users"] | undefined = undefined; let user: DB["users"] | undefined;
if (params.id) { if (params.id) {
const { data } = await this.userRepo.findId(params.id); const { data } = await this.userRepo.findId(params.id);
user = data; user = data;
@@ -225,26 +226,33 @@ export class AuthController extends Controller {
}; };
const roles = Object.keys(this.auth.config.roles ?? {}); const roles = Object.keys(this.auth.config.roles ?? {});
mcp.tool( try {
"auth_user_create", const actions = this.auth.authenticator.strategy("password").getActions();
{ if (actions.create) {
description: "Create a new user", const schema = actions.create.schema;
inputSchema: s.object({ mcp.tool(
email: s.string({ format: "email" }), "auth_user_create",
password: s.string({ minLength: 8 }), {
role: s description: "Create a new user",
.string({ inputSchema: s.object({
enum: roles.length > 0 ? roles : undefined, ...schema.properties,
}) role: s
.optional(), .string({
}), enum: roles.length > 0 ? roles : undefined,
}, })
async (params, c) => { .optional(),
await c.context.ctx().helper.granted(c, AuthPermissions.createUser); }),
},
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( mcp.tool(
"auth_user_token", "auth_user_token",

View File

@@ -10,6 +10,7 @@ const schema = s
.object({ .object({
hashing: s.string({ enum: ["plain", "sha256", "bcrypt"], default: "sha256" }), hashing: s.string({ enum: ["plain", "sha256", "bcrypt"], default: "sha256" }),
rounds: s.number({ minimum: 1, maximum: 10 }).optional(), rounds: s.number({ minimum: 1, maximum: 10 }).optional(),
minLength: s.number({ default: 8, minimum: 1 }).optional(),
}) })
.strict(); .strict();
@@ -37,7 +38,7 @@ export class PasswordStrategy extends AuthStrategy<typeof schema> {
format: "email", format: "email",
}), }),
password: s.string({ password: s.string({
minLength: 8, // @todo: this should be configurable minLength: this.config.minLength,
}), }),
}); });
} }
@@ -65,12 +66,21 @@ export class PasswordStrategy extends AuthStrategy<typeof schema> {
return await bcryptCompare(compare, actual); return await bcryptCompare(compare, actual);
} }
return false; return actual === compare;
} }
verify(password: string) { verify(password: string) {
return async (user: User) => { 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) { if (compare !== true) {
throw new InvalidCredentialsException(); throw new InvalidCredentialsException();
} }

View File

@@ -103,7 +103,7 @@ export function AuthForm({
</Group> </Group>
<Group> <Group>
<Label htmlFor="password">Password</Label> <Label htmlFor="password">Password</Label>
<Password name="password" required minLength={8} /> <Password name="password" required minLength={1} />
</Group> </Group>
<Button <Button