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,
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",

View File

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

View File

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