- 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>
73 lines
2.1 KiB
TypeScript
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;
|
|
}
|
|
}
|