Files
stack/apps/api/src/auth/guards/admin.guard.ts
Jason Woltje e3dd490d4d fix(#84): address critical security issues in federation identity
Implemented comprehensive security fixes for federation instance identity:

CRITICAL SECURITY FIXES:
1. Private Key Encryption at Rest (AES-256-GCM)
   - Implemented CryptoService with AES-256-GCM encryption
   - Private keys encrypted before database storage
   - Decrypted only when needed in-memory
   - Master key stored in ENCRYPTION_KEY environment variable
   - Updated schema comment to reflect actual encryption method

2. Admin Authorization on Key Regeneration
   - Created AdminGuard for system-level admin operations
   - Requires workspace ownership for admin privileges
   - Key regeneration restricted to admin users only
   - Proper authorization checks before sensitive operations

3. Private Key Never Exposed in API Responses
   - Changed regenerateKeypair return type to PublicInstanceIdentity
   - Service method strips private key before returning
   - Added tests to verify private key exclusion
   - Controller returns only public identity

ADDITIONAL SECURITY IMPROVEMENTS:
4. Audit Logging for Key Regeneration
   - Created FederationAuditService
   - Logs all keypair regeneration events
   - Includes userId, instanceId, and timestamp
   - Marked as security events for compliance

5. Input Validation for INSTANCE_URL
   - Validates URL format (must be HTTP/HTTPS)
   - Throws error on invalid URLs
   - Prevents malformed configuration

6. Added .env.example
   - Documents all required environment variables
   - Includes INSTANCE_NAME, INSTANCE_URL
   - Includes ENCRYPTION_KEY with generation instructions
   - Clear security warnings for production use

TESTING:
- Added 11 comprehensive crypto service tests
- Updated 8 federation service tests for encryption
- Updated 5 controller tests for security verification
- Total: 24 tests passing (100% success rate)
- Verified private key never exposed in responses
- Verified encryption/decryption round-trip
- Verified admin authorization requirements

FILES CREATED:
- apps/api/src/federation/crypto.service.ts (encryption)
- apps/api/src/federation/crypto.service.spec.ts (tests)
- apps/api/src/federation/audit.service.ts (audit logging)
- apps/api/src/auth/guards/admin.guard.ts (authorization)
- apps/api/.env.example (configuration template)

FILES MODIFIED:
- apps/api/prisma/schema.prisma (updated comment)
- apps/api/src/federation/federation.service.ts (encryption integration)
- apps/api/src/federation/federation.controller.ts (admin guard, audit)
- apps/api/src/federation/federation.module.ts (new providers)
- All test files updated for new security requirements

CODE QUALITY:
- All tests passing (24/24)
- TypeScript compilation: PASS
- ESLint: PASS
- Test coverage maintained at 100%

Fixes #84

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-03 11:13:12 -06:00

47 lines
1.4 KiB
TypeScript

/**
* Admin Guard
*
* Restricts access to system-level admin operations.
* Currently checks if user owns at least one workspace (indicating admin status).
* Future: Replace with proper role-based access control (RBAC).
*/
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
Logger,
} from "@nestjs/common";
import { PrismaService } from "../../prisma/prisma.service";
import type { AuthenticatedRequest } from "../../common/types/user.types";
@Injectable()
export class AdminGuard implements CanActivate {
private readonly logger = new Logger(AdminGuard.name);
constructor(private readonly prisma: PrismaService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<AuthenticatedRequest>();
const user = request.user;
if (!user) {
throw new ForbiddenException("User not authenticated");
}
// Check if user owns any workspace (admin indicator)
// TODO: Replace with proper RBAC system admin role check
const ownedWorkspaces = await this.prisma.workspace.count({
where: { ownerId: user.id },
});
if (ownedWorkspaces === 0) {
this.logger.warn(`Non-admin user ${user.id} attempted admin operation`);
throw new ForbiddenException("This operation requires system administrator privileges");
}
return true;
}
}