mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
feat: add minLength to pw strategy, and enforce
This commit is contained in:
13
app/__test__/auth/strategies/PasswordStrategy.spec.ts
Normal file
13
app/__test__/auth/strategies/PasswordStrategy.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user