Skip to Content
🚀 APSO is now in public beta. Get started →
ArchitectureSecurity

Security Model

APSO implements security at multiple layers, from authentication to data isolation.

Security Layers

Authentication

JWT Token Flow

Token Structure

{ "header": { "alg": "RS256", "typ": "JWT" }, "payload": { "sub": "user-uuid", "email": "user@example.com", "organizationId": "org-uuid", "roles": ["admin"], "iat": 1699000000, "exp": 1699086400 } }

Token Validation

// JWT verification middleware @Injectable() export class AuthGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const token = this.extractToken(request); if (!token) { throw new UnauthorizedException('No token provided'); } try { const payload = await this.jwtService.verifyAsync(token, { secret: this.configService.get('JWT_SECRET'), }); // Attach user context request.user = { id: payload.sub, email: payload.email, organizationId: payload.organizationId, roles: payload.roles || [], }; return true; } catch { throw new UnauthorizedException('Invalid token'); } } }

Multi-Tenancy

Organization Isolation

All data is scoped by organization:

// Every query includes organization filter async findAll(user: User): Promise<Project[]> { return this.repository.find({ where: { organizationId: user.organizationId, // Always scoped }, }); }

Scoping Levels

LevelDescriptionUse Case
organizationVisible to all org membersProjects, settings
userVisible only to creatorDrafts, preferences
globalVisible across all orgsShared resources
publicNo authentication requiredPublic content

Data Scoping Configuration

{ "entities": [ { "name": "Project", "scoping": "organization" }, { "name": "UserSettings", "scoping": "user" } ] }

Authorization

Role-Based Access Control

// Define roles enum Role { ADMIN = 'admin', MEMBER = 'member', VIEWER = 'viewer', } // Role guard @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler()); if (!requiredRoles) return true; const { user } = context.switchToHttp().getRequest(); return requiredRoles.some(role => user.roles?.includes(role)); } } // Usage @Roles(Role.ADMIN) @Delete(':id') remove(@Param('id') id: string) { return this.service.remove(id); }

Resource-Level Permissions

// Check ownership before modification async update(id: string, dto: UpdateProjectDto, user: User) { const project = await this.repository.findOne({ where: { id, organizationId: user.organizationId }, }); if (!project) { throw new NotFoundException(); } // Additional permission check if (project.ownerId !== user.id && !user.roles.includes('admin')) { throw new ForbiddenException('Not allowed to modify this resource'); } return this.repository.save({ ...project, ...dto }); }

Input Validation

Request Validation

All inputs are validated using class-validator:

export class CreateProjectDto { @IsString() @IsNotEmpty() @MaxLength(200) @Matches(/^[a-zA-Z0-9\s\-_]+$/, { message: 'Name contains invalid characters', }) name: string; @IsOptional() @IsString() @MaxLength(5000) description?: string; @IsOptional() @IsUUID() parentId?: string; }

SQL Injection Prevention

TypeORM parameterized queries prevent SQL injection:

// Safe - parameterized const results = await this.repository .createQueryBuilder('project') .where('project.name LIKE :search', { search: `%${query}%` }) .getMany(); // Never do this - vulnerable // .where(`name LIKE '%${query}%'`)

XSS Protection

Output encoding for all responses:

// Sanitize HTML content import { sanitize } from 'class-sanitizer'; export class CreateCommentDto { @Transform(({ value }) => sanitize(value)) @IsString() content: string; }

Security Headers

// Helmet middleware for security headers app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], }, }, hsts: { maxAge: 31536000, includeSubDomains: true, }, }));

Rate Limiting

// Throttle decorator @Throttle({ default: { limit: 100, ttl: 60000 } }) @Controller('projects') export class ProjectsController {} // Per-route limits @Throttle({ default: { limit: 5, ttl: 60000 } }) @Post('upload') uploadFile() {}

Audit Logging

// Log security-relevant events @Injectable() export class AuditLogger { log(event: { action: string; userId: string; resourceType: string; resourceId: string; metadata?: Record<string, any>; }) { console.log(JSON.stringify({ timestamp: new Date().toISOString(), ...event, })); } } // Usage this.auditLogger.log({ action: 'DELETE', userId: user.id, resourceType: 'Project', resourceId: id, });

Security Checklist

  • JWT tokens with short expiration
  • HTTPS enforced in production
  • Input validation on all endpoints
  • SQL injection prevention via ORM
  • XSS protection via output encoding
  • CORS configuration
  • Rate limiting
  • Security headers (HSTS, CSP)
  • Multi-tenant data isolation
  • Audit logging
Last updated on