v3: added a roles/permissions system, added user management tab on user profile
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s

This commit is contained in:
khannurien
2026-06-28 06:21:23 +00:00
parent b567e390d9
commit 810faaf21a
22 changed files with 341 additions and 60 deletions

View File

@@ -1,4 +1,9 @@
import { Context, Next, State } from "@oak/oak";
import {
type Next,
type RouteParams,
type RouterContext,
type State,
} from "@oak/oak";
import { verifyJWT } from "../lib/jwt.ts";
import {
APIErrorCode,
@@ -6,16 +11,24 @@ import {
type AuthPayload,
} from "../model/interfaces.ts";
import { getUserById } from "../services/user-service.ts";
export interface AuthContext extends Context {
state: AuthState;
}
import { can, type Permission } from "../lib/permissions.ts";
export interface AuthState extends State {
user: AuthPayload;
}
export async function authMiddleware(ctx: AuthContext, next: Next) {
// A router context whose state is guaranteed to carry the authenticated user
// (populated by authMiddleware). Generic over the route path so `ctx.params`
// stays typed per-route — e.g. `AuthContext<"/:userId/role">`.
export type AuthContext<
R extends string = string,
P extends RouteParams<R> = RouteParams<R>,
> = RouterContext<R, P, AuthState>;
export async function authMiddleware<R extends string>(
ctx: AuthContext<R>,
next: Next,
): Promise<void> {
const authHeader = ctx.request.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
@@ -44,8 +57,11 @@ export async function authMiddleware(ctx: AuthContext, next: Next) {
await next();
}
export async function adminOnlyMiddleware(ctx: AuthContext, next: Next) {
if (!ctx.state.user?.isAdmin) {
export async function adminOnlyMiddleware<R extends string>(
ctx: AuthContext<R>,
next: Next,
): Promise<void> {
if (ctx.state.user.role !== "admin") {
throw new APIException(
APIErrorCode.UNAUTHORIZED,
403,
@@ -55,3 +71,21 @@ export async function adminOnlyMiddleware(ctx: AuthContext, next: Next) {
await next();
}
// Gates a route on a named permission. Must run after authMiddleware. Generic
// over the route path so it composes with path-typed route handlers.
export function requirePermission(permission: Permission) {
return async <R extends string>(
ctx: AuthContext<R>,
next: Next,
): Promise<void> => {
if (!ctx.state.user || !can(ctx.state.user, permission)) {
throw new APIException(
APIErrorCode.UNAUTHORIZED,
403,
"Insufficient permissions",
);
}
await next();
};
}