Skip to Content
APSO is 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