Files
stack/apps/api/src/auth/guards/admin.guard.ts
Jason Woltje 06de72a355 fix(#338): Implement proper system admin role separate from workspace ownership
- Replace workspace ownership check with explicit SYSTEM_ADMIN_IDS env var
- System admin access is now explicit and configurable via environment
- Workspace owners no longer automatically get system admin privileges
- Add 15 unit tests verifying security separation
- Add SYSTEM_ADMIN_IDS documentation to .env.example

Refs #338

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 16:44:50 -06:00

73 lines
2.1 KiB
TypeScript

/**
* Admin Guard
*
* Restricts access to system-level admin operations.
* System administrators are configured via the SYSTEM_ADMIN_IDS environment variable.
*
* Configuration:
* SYSTEM_ADMIN_IDS=uuid1,uuid2,uuid3 (comma-separated list of user IDs)
*
* Note: Workspace ownership does NOT grant system admin access. These are separate concepts:
* - Workspace owner: Can manage their workspace and its members
* - System admin: Can perform system-level operations across all workspaces
*/
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
Logger,
} from "@nestjs/common";
import type { AuthenticatedRequest } from "../../common/types/user.types";
@Injectable()
export class AdminGuard implements CanActivate {
private readonly logger = new Logger(AdminGuard.name);
private readonly systemAdminIds: Set<string>;
constructor() {
// Load system admin IDs from environment variable
const adminIdsEnv = process.env.SYSTEM_ADMIN_IDS ?? "";
this.systemAdminIds = new Set(
adminIdsEnv
.split(",")
.map((id) => id.trim())
.filter((id) => id.length > 0)
);
if (this.systemAdminIds.size === 0) {
this.logger.warn(
"No system administrators configured. Set SYSTEM_ADMIN_IDS environment variable."
);
} else {
this.logger.log(
`System administrators configured: ${String(this.systemAdminIds.size)} user(s)`
);
}
}
/**
* Check if a user ID is a system administrator
*/
isSystemAdmin(userId: string): boolean {
return this.systemAdminIds.has(userId);
}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<AuthenticatedRequest>();
const user = request.user;
if (!user) {
throw new ForbiddenException("User not authenticated");
}
if (!this.isSystemAdmin(user.id)) {
this.logger.warn(`Non-admin user ${user.id} attempted admin operation`);
throw new ForbiddenException("This operation requires system administrator privileges");
}
return true;
}
}