fix(web): admin page role check — stop false redirect to /chat (#203)
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #203.
This commit is contained in:
@@ -8,8 +8,11 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { fromNodeHeaders } from 'better-auth/node';
|
import { fromNodeHeaders } from 'better-auth/node';
|
||||||
import type { Auth } from '@mosaic/auth';
|
import type { Auth } from '@mosaic/auth';
|
||||||
|
import type { Db } from '@mosaic/db';
|
||||||
|
import { eq, users as usersTable } from '@mosaic/db';
|
||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
import { AUTH } from '../auth/auth.tokens.js';
|
import { AUTH } from '../auth/auth.tokens.js';
|
||||||
|
import { DB } from '../database/database.module.js';
|
||||||
|
|
||||||
interface UserWithRole {
|
interface UserWithRole {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -18,7 +21,10 @@ interface UserWithRole {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminGuard implements CanActivate {
|
export class AdminGuard implements CanActivate {
|
||||||
constructor(@Inject(AUTH) private readonly auth: Auth) {}
|
constructor(
|
||||||
|
@Inject(AUTH) private readonly auth: Auth,
|
||||||
|
@Inject(DB) private readonly db: Db,
|
||||||
|
) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const request = context.switchToHttp().getRequest<FastifyRequest>();
|
const request = context.switchToHttp().getRequest<FastifyRequest>();
|
||||||
@@ -32,7 +38,21 @@ export class AdminGuard implements CanActivate {
|
|||||||
|
|
||||||
const user = result.user as UserWithRole;
|
const user = result.user as UserWithRole;
|
||||||
|
|
||||||
if (user.role !== 'admin') {
|
// Ensure the role field is populated. better-auth should include additionalFields
|
||||||
|
// in the session, but as a fallback, fetch the role from the database if needed.
|
||||||
|
let userRole = user.role;
|
||||||
|
if (!userRole) {
|
||||||
|
const [dbUser] = await this.db
|
||||||
|
.select({ role: usersTable.role })
|
||||||
|
.from(usersTable)
|
||||||
|
.where(eq(usersTable.id, user.id))
|
||||||
|
.limit(1);
|
||||||
|
userRole = dbUser?.role ?? 'member';
|
||||||
|
// Update the session user object with the fetched role
|
||||||
|
(user as UserWithRole).role = userRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userRole !== 'admin') {
|
||||||
throw new ForbiddenException('Admin access required');
|
throw new ForbiddenException('Admin access required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
docs/scratchpads/bug-196-admin-redirect.md
Normal file
37
docs/scratchpads/bug-196-admin-redirect.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# BUG-196: Admin Page Redirect Issue
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Admin page redirects to /chat for users with admin role because role check fails.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
The `role` field is defined as an `additionalField` in better-auth's user configuration, but
|
||||||
|
better-auth v1.5.5 does not automatically include additionalFields in the session response from
|
||||||
|
the `getSession()` API. This causes the admin role check to fail:
|
||||||
|
|
||||||
|
- Frontend: `AdminRoleGuard` checks `user?.role !== 'admin'`
|
||||||
|
- Backend: `AdminGuard` checks `user.role !== 'admin'`
|
||||||
|
- When `role` is `undefined`, both checks treat the user as non-admin and deny access
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Implemented a defensive check in the backend `AdminGuard` that:
|
||||||
|
|
||||||
|
1. First tries to use the `role` field from the session (if better-auth includes it)
|
||||||
|
2. Falls back to fetching the role directly from the database if it's missing
|
||||||
|
3. Defaults to 'member' if the user has no role set
|
||||||
|
|
||||||
|
This ensures that admin users can always access the admin panel, and also protects against
|
||||||
|
the case where better-auth doesn't include the additionalField in future versions.
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
|
||||||
|
1. `/apps/gateway/src/admin/admin.guard.ts` - Added fallback role lookup
|
||||||
|
2. `/packages/auth/src/auth.ts` - No changes needed (better-auth config is correct)
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- All three quality gates pass: `typecheck`, `lint`, `format:check`
|
||||||
|
- Backend admin guard now explicitly handles missing role field
|
||||||
|
- Frontend admin guard remains unchanged (will work once role is available)
|
||||||
Reference in New Issue
Block a user