added additional permissions, implemented mcp authentication

This commit is contained in:
dswbx
2025-08-07 15:20:29 +02:00
parent 42db5f55c7
commit 170ea2c45b
16 changed files with 144 additions and 74 deletions

View File

@@ -236,6 +236,8 @@ export class AuthController extends Controller {
}),
},
async (params, c) => {
await c.context.ctx().helper.throwUnlessGranted(AuthPermissions.createUser, c);
return c.json(await this.auth.createUser(params));
},
);
@@ -251,6 +253,8 @@ export class AuthController extends Controller {
}),
},
async (params, c) => {
await c.context.ctx().helper.throwUnlessGranted(AuthPermissions.createToken, c);
const user = await getUser(params);
return c.json({ user, token: await this.auth.authenticator.jwt(user) });
},
@@ -268,6 +272,8 @@ export class AuthController extends Controller {
}),
},
async (params, c) => {
await c.context.ctx().helper.throwUnlessGranted(AuthPermissions.changePassword, c);
const user = await getUser(params);
if (!(await this.auth.changePassword(user.id, params.password))) {
throw new Error("Failed to change password");
@@ -287,6 +293,8 @@ export class AuthController extends Controller {
}),
},
async (params, c) => {
await c.context.ctx().helper.throwUnlessGranted(AuthPermissions.testPassword, c);
const pw = this.auth.authenticator.strategy("password") as PasswordStrategy;
const controller = pw.getController(this.auth.authenticator);

View File

@@ -2,3 +2,6 @@ import { Permission } from "core/security/Permission";
export const createUser = new Permission("auth.user.create");
//export const updateUser = new Permission("auth.user.update");
export const testPassword = new Permission("auth.user.password.test");
export const changePassword = new Permission("auth.user.password.change");
export const createToken = new Permission("auth.user.token.create");

View File

@@ -42,27 +42,25 @@ export interface UserPool {
}
const defaultCookieExpires = 60 * 60 * 24 * 7; // 1 week in seconds
export const cookieConfig = $object("config_auth_cookie", {
path: s.string({ default: "/" }),
sameSite: s.string({ enum: ["strict", "lax", "none"], default: "lax" }),
secure: s.boolean({ default: true }),
httpOnly: s.boolean({ default: true }),
expires: s.number({ default: defaultCookieExpires }), // seconds
partitioned: s.boolean({ default: false }),
renew: s.boolean({ default: true }),
pathSuccess: s.string({ default: "/" }),
pathLoggedOut: s.string({ default: "/" }),
})
.partial()
.strict();
export const cookieConfig = s
.strictObject({
path: s.string({ default: "/" }),
sameSite: s.string({ enum: ["strict", "lax", "none"], default: "lax" }),
secure: s.boolean({ default: true }),
httpOnly: s.boolean({ default: true }),
expires: s.number({ default: defaultCookieExpires }), // seconds
partitioned: s.boolean({ default: false }),
renew: s.boolean({ default: true }),
pathSuccess: s.string({ default: "/" }),
pathLoggedOut: s.string({ default: "/" }),
})
.partial();
// @todo: maybe add a config to not allow cookie/api tokens to be used interchangably?
// see auth.integration test for further details
export const jwtConfig = $object(
"config_auth_jwt",
export const jwtConfig = s.strictObject(
{
// @todo: autogenerate a secret if not present. But it must be persisted from AppAuth
secret: secret({ default: "" }),
alg: s.string({ enum: ["HS256", "HS384", "HS512"], default: "HS256" }).optional(),
expires: s.number().optional(), // seconds
@@ -72,7 +70,7 @@ export const jwtConfig = $object(
{
default: {},
},
).strict();
);
export const authenticatorConfig = s.object({
jwt: jwtConfig,
@@ -378,13 +376,24 @@ export class Authenticator<
}
// @todo: don't extract user from token, but from the database or cache
async resolveAuthFromRequest(c: Context): Promise<SafeUser | undefined> {
let token: string | undefined;
if (c.req.raw.headers.has("Authorization")) {
const bearerHeader = String(c.req.header("Authorization"));
token = bearerHeader.replace("Bearer ", "");
async resolveAuthFromRequest(c: Context | Request | Headers): Promise<SafeUser | undefined> {
let headers: Headers;
let is_context = false;
if (c instanceof Headers) {
headers = c;
} else if (c instanceof Request) {
headers = c.headers;
} else {
token = await this.getAuthCookie(c);
is_context = true;
headers = c.req.raw.headers;
}
let token: string | undefined;
if (headers.has("Authorization")) {
const bearerHeader = String(headers.get("Authorization"));
token = bearerHeader.replace("Bearer ", "");
} else if (is_context) {
token = await this.getAuthCookie(c as Context);
}
if (token) {