feat(admin): web admin panel — user CRUD, role assignment, system health (#150)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #150.
This commit is contained in:
2026-03-15 19:18:47 +00:00
committed by jason.woltje
parent 22a5e9791c
commit a760401407
12 changed files with 890 additions and 81 deletions

View File

@@ -0,0 +1,44 @@
import {
CanActivate,
ExecutionContext,
ForbiddenException,
Inject,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { fromNodeHeaders } from 'better-auth/node';
import type { Auth } from '@mosaic/auth';
import type { FastifyRequest } from 'fastify';
import { AUTH } from '../auth/auth.tokens.js';
interface UserWithRole {
id: string;
role?: string;
}
@Injectable()
export class AdminGuard implements CanActivate {
constructor(@Inject(AUTH) private readonly auth: Auth) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<FastifyRequest>();
const headers = fromNodeHeaders(request.raw.headers);
const result = await this.auth.api.getSession({ headers });
if (!result) {
throw new UnauthorizedException('Invalid or expired session');
}
const user = result.user as UserWithRole;
if (user.role !== 'admin') {
throw new ForbiddenException('Admin access required');
}
(request as FastifyRequest & { user: unknown; session: unknown }).user = result.user;
(request as FastifyRequest & { user: unknown; session: unknown }).session = result.session;
return true;
}
}