Skip to main content

Middleware Design

Middleware Design Document

Project: Drop{{PROJECT_NAME}} Version: 0.1.0{{VERSION}} Date: 2026-02-23{{DATE}} Author: Platform Architect (AI){{AUTHOR}} Status: Draft | In Review | Approved Reviewers: Alem Bašić (CEO){{REVIEWERS}}

Document History

Version Date Author Changes
0.1 2026-02-23{{DATE}} Platform Architect (AI){{AUTHOR}} Initial draft from source code analysis

1. Middleware Pipeline Overview

Drop has

sequenceDiagram
    twoparticipant middlewareClient
    layers:

participant
    CORS
  1. as 1. CORS participant Security as 2. Security Headers participant RequestID as 3. Request ID participant RateLimit as 4. Rate Limiter participant Logger as 5. Request Logger participant Auth as 6. Authentication participant Authz as 7. Authorization (RBAC) participant Validate as 8. Validation participant AuditLog as 9. Audit Logger participant Handler as Route Handler Client->>CORS: HTTP Request CORS->>Security: (CORS headers set) Security->>RequestID: (Security headers set) RequestID->>RateLimit: (X-Request-ID injected) RateLimit->>Logger: (Rate check passed) Logger->>Auth: (Request logged) Auth->>Authz: (JWT validated, user attached) Authz->>Validate: (Permissions verified) Validate->>AuditLog: (Input validated & sanitized) AuditLog->>Handler: (Audit record written) Handler-->>Client: Response

Framework: src/lib/middleware.ts{{NestJS / Express / Fastify / Hono}} Execution order is strictThechanging activeorder middlewaremay usedbreak bysecurity all API routes. Provides requireAuth, requireMerchant, rateLimit, getClientIp, jsonError, CSRF protection, and session revocation.

  • src/lib/middleware/ — A modular middleware library with auth-middleware.ts (Bearer token for mobile), error-handler.ts (AppError class), and validation.ts (input sanitization functions).

  • Both layers are used in production. Routes import from @/lib/middleware (auth, rate limiting) and @/lib/middleware/validation (input validation).guarantees.


    2. ActiveRequest Middleware (lib/middleware.ts)Lifecycle

    2.1 requireAuth(request?)CORS Middleware

    Source: middleware.ts:42–80

    Authenticates the current request via cookie-based JWT.

    Returns:Library: {{cors user:/ User, error: null } | { user: null, error: NextResponse @fastify/cors}}

    Steps:

    1. CSRF origin check — if Origin header present, must match allowed origins (NEXT_PUBLIC_APP_URL, http://localhost:3000, http://localhost:3001)
    2. Cookie extraction — reads drop_token from request cookies
    3. JWT verification — validates HS256 signature and expiry using jose library
    4. User lookup — loads user from users table by userId from JWT payload
    5. Session revocation check — verifies at least one non-revoked session exists for this user

    Usage:Configuration:

    // config/cors.config.ts
    export const corsConfig = {
      user,origin: error(origin: }string, callback: Function) => {
        const allowedOrigins = await[
          requireAuth(request)'https://app.{{domain.com}}',
          'https://admin.{{domain.com}}',
          ...(process.env.NODE_ENV !== 'production'
            ? ['http://localhost:3000', 'http://localhost:3001']
            : []),
        ];
    
        if (error)!origin return|| error;allowedOrigins.includes(origin)) {
          callback(null, true);
        } else {
          callback(new Error(`Origin ${origin} not allowed by CORS`));
        }
      },
      methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
      allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
      exposedHeaders: ['X-Request-ID', 'X-RateLimit-Limit', 'X-RateLimit-Remaining'],
      credentials: true,
      maxAge: 86400, // Returns24h NextResponsepreflight withcache
    JSON error
    // user is guaranteed non-null here};
    

    ErrorPerformance responses:impact: < 0.1ms per request (header injection only)

    • 401 unauthorized — missing cookie, invalid JWT, expired token, user not found, all sessions revoked

    2.2 requireMerchant(request?)Security Headers Middleware

    Source:Library: middleware.ts:101–108helmet

    Extends requireAuth with a merchant role check.

    const app.use(helmet({ user, error } = await requireMerchant(request);
    if (error) return error;
      // 401Content ifSecurity notPolicy
      authenticated,contentSecurityPolicy: 403{
        ifdirectives: not{
          merchantdefaultSrc: 
    ["'self'"],

    ReturnsscriptSrc: 403["'self'"], forbiddenstyleSrc: if["'self'", user"'unsafe-inline'"], existsimgSrc: but["'self'", role"data:", !=="https://cdn.{{domain.com}}"], connectSrc: ["'self'", "https://api.{{domain.com}}"], frameSrc: ["'none'"], objectSrc: ["'none'"], }, }, // HTTP Strict Transport Security hsts: { maxAge: 31536000, // 1 year includeSubDomains: true, preload: true, }, // Other headers referrerPolicy: { policy: 'merchant'.

    same-origin'

    Applied}, to:frameguard: GET{ action: 'deny' }, noSniff: true, /api/merchants/dashboard,/ GETX-Content-Type-Options: nosniff xssFilter: true, /api/merchants/qr,/ GETX-XSS-Protection (legacy browsers) hidePoweredBy: true, /api/merchants/transactions

    /
    Remove

    2.3X-Powered-By rateLimit(ip, limit, windowMs?})

    Source: middleware.ts:7–31

    Persistent IP-based rate limiter using the rate_limits database table.

    ParameterDefaultDescription
    ipClient IP address
    limitMax requests per window
    windowMs60,000msWindow size in milliseconds

    Returns: boolean — true if request is allowed, false if rate limited.

    Implementation:

    • Uses runUpsert for atomic counter creation/update
    • Cleans expired entries on each call (removes rows where expires_at < now)
    • Counter stored in rate_limits table: (key, count, expires_at)

    Rate limit table schema:

    CREATE TABLE rate_limits (
      key TEXT PRIMARY KEY,      -- IP address
      count INTEGER DEFAULT 1,
      expires_at INTEGER         -- Unix timestamp (ms)
    );
    

    Usage:

    const ip = getClientIp(request);
    if (!(await rateLimit(ip, 10))) {  // 10 req/min
      return jsonError("rate_limited", "Too many requests", 429);
    }
    

    Applied limits:

    EndpointLimitWindow
    /api/auth/bankid/initiate10/min60s
    /api/auth/bankid/callback10/min60s
    /api/auth/register (deprecated)10/min60s
    /api/auth/login (deprecated)10/min60s
    /api/transactions/remittance10/min60s
    /api/transactions/qr-payment10/min60s
    /api/rates120/min60s
    /api/rates/[currency]120/min60s

    2.4 getClientIp(request)

    Source: middleware.ts:33–35

    Extracts the client's real IP address from the x-forwarded-for header (first IP in the chain — the originating client). Falls back to '127.0.0.1' if header not present.

    Note: When behind App Runner (AWS managed proxy), x-forwarded-for is set automatically with the real client IP.


    2.5 jsonError(error, message, status, details?)

    Source: middleware.ts:37–39

    Creates a standardized JSON error NextResponse.

    return jsonError("validation_error", "Validation failed", 422, ["Email required"]);
    // Response body: { "error": "validation_error", "message": "Validation failed", "details": ["Email required"] }
    

    2.6 revokeAllSessions(userId)

    Source: middleware.ts:83–85

    Sets revoked=1 on all sessions for a user. Called by POST /api/auth/logout.

    UPDATE sessions SET revoked = 1 WHERE user_id = $1;
    

    2.7 generateCsrfToken() / validateCsrf(request, token)

    Source: middleware.ts:88–99

    CSRF token generation (32 random bytes hex-encoded) and validation via x-csrf-token header.

    Status: Implemented but not actively required on any route. CSRF protection is handled via:

    • BankID OIDC state parameter (login flow)
    • Origin header validation (in requireAuth)

    3. Middleware Library (lib/middleware/)

    3.1 Error Handler (middleware/error-handler.ts)

    AppError class:

    class AppError extends Error {
      constructor(
        public code: string,
        message: string,
        public status: number = 500,
        public details?: unknown
      ) {}
    }
    

    Predefined error constructors:

    ConstructorCodeHTTP Status
    Errors.unauthorized(msg?)UNAUTHORIZED401
    Errors.forbidden(msg?)FORBIDDEN403
    Errors.notFound(resource)NOT_FOUND404
    Errors.badRequest(msg, details?)BAD_REQUEST400
    Errors.conflict(msg)CONFLICT409
    Errors.tooManyRequests(msg?)RATE_LIMIT_EXCEEDED429
    Errors.internal(msg?)INTERNAL_ERROR500

    Error response format:

    {
      "error": {
        "code": "BAD_REQUEST",
        "message": "Amount must be between 100 and 50000 NOK",
        "details": "validation_error"
      }
    }
    

    Production masking: createErrorResponse() masks internal error messages in production — only returns "An unexpected error occurred" for 500 errors.


    3.2 Auth Middleware (middleware/auth-middleware.ts)

    Alternative auth middleware for mobile clients using Bearer token pattern.

    requireAuth(request):

    • Extracts JWT from Authorization: Bearer <token> header
    • Verifies JWT signature + expiry
    • Returns userId from payload

    In-memory rate limiter (for Bearer token routes):

    • DEFAULT_RATE_LIMIT: 100 req/min
    • STRICT_RATE_LIMIT: 10 req/min
    • Auto-cleanup every 5 minutes
    • Rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

    getClientIP(request): Checks X-Forwarded-For → X-Real-IP → falls back to 'unknown'.


    3.3 Validation (middleware/validation.ts)

    Input validation functions — no external dependencies, all custom implementations.

    FunctionDescriptionRules
    validatePhone(phone)International phoneStarts with +, 8–15 digits
    validateAmount(amount)Positive monetary amount> 0, max 2 decimal places
    validateIBAN(iban)European IBANCountry code + alphanumeric, mod-97 checksum
    validatePIN(pin)Card PINExactly 4 digits
    validateEmail(email)Email addressBasic [email protected] pattern
    validateCurrency(currency)ISO 4217 codeWhitelist: EUR, USD, GBP, BAM, CHF, PLN, NOK, RSD, TRY, PKR
    validateDateISO(date)ISO 8601 dateParseable by Date.parse()
    validateName(name)Name field1–100 chars, at least one letter, XSS-safe
    validateLanguage(lang)Language codeWhitelist: nb, en, bs, sq
    sanitizeText(text, maxLength?)Text sanitizationStrips HTML tags + control chars, trims, enforces max length (default 500)
    validate(condition, msg)Assert helperThrows AppError (400) if false
    required(value, name)Required field checkThrows AppError (400) if null/undefined

    Security notes:

    • validateName checks for: <script, javascript:, onerror=, onclick= — blocks XSS injection in name fields
    • sanitizeText removes HTML tags via regex, strips control characters
    • validateIBAN implements full mod-97 checksum algorithm
    • validateAmount rejects NaN, Infinity, negative values

    4. Security Headers (Next.js Config)

    Applied to all responses via next.config.ts:set:

    Header Production ValueDevelopment Value Purpose
    Strict-Transport-Securitymax-age=31536000; includeSubDomains; preloadForce HTTPS
    Content-Security-Policy default-src(see 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self'; frame-ancestors 'none'Adds 'unsafe-eval' + 'unsafe-inline' for HMRabove) XSS protectionprevention
    X-Frame-OptionsDENY DENY Clickjacking prevention
    X-Content-Type-Options nosniff nosniffMIME sniffing prevention
    Referrer-Policy strict-origin-when-cross-same-origin SameReferrer leakage prevention
    Permissions-Policycamera=(self), microphone=(), geolocation=(self)SameFeature restriction
    Strict-Transport-Securitymax-age=63072000; includeSubDomains; preloadSameForce HTTPS (2-year HSTS)privacy

    Performance impact: < 0.2ms per request


    5.

    2.3 Request ID Middleware

    Purpose: Correlate logs across services for distributed tracing.

    // middleware/request-id.middleware.ts
    export function requestIdMiddleware(req: Request, res: Response, next: NextFunction) {
      const requestId = req.headers['x-request-id'] as string
        || `req_${ulid()}`;
    
      req.requestId = requestId;
      res.setHeader('X-Request-ID', requestId);
    
      // Bind to AsyncLocalStorage for log correlation
      requestContext.run({ requestId }, next);
    }
    

    Format: req_{ulid} — e.g., req_01HX7M2K5N3P4Q5R6S7T8V9W0


    2.4 Authentication Middleware

    Strategy: JWT Bearer token validation

    // guards/jwt.guard.ts
    @Injectable()
    export class JwtGuard 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'),
            algorithms: ['HS256'],
            clockTolerance: 10, // 10 second clock skew tolerance
          });
    
          // Attach to request for downstream use
          request.user = {
            id: payload.sub,
            email: payload.email,
            role: payload.role,
          };
    
          return true;
        } catch (error) {
          if (error instanceof TokenExpiredError) {
            throw new UnauthorizedException('TOKEN_EXPIRED');
          }
          throw new UnauthorizedException('INVALID_TOKEN');
        }
      }
    
      private extractToken(request: Request): string | null {
        const [type, token] = request.headers.authorization?.split(' ') ?? [];
        return type === 'Bearer' ? token : null;
      }
    }
    

    Performance impact: ~2-5ms (crypto operation + optional DB lookup for token revocation)

    Token revocation check: {{Check Redis blocklist on each request | Check on logout only | Use short TTL — no revocation check}}


    2.5 Authorization Middleware (RBAC/ABAC)

    Model: {{RBAC (Role-Based) | ABAC (Attribute-Based) | Hybrid}}

    // decorators/roles.decorator.ts
    export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
    export const RequirePermission = (permission: string) =>
      SetMetadata('permission', permission);
    
    // guards/roles.guard.ts
    @Injectable()
    export class RolesGuard implements CanActivate {
      canActivate(context: ExecutionContext): boolean {
        const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler());
        const requiredPermission = this.reflector.get<string>('permission', context.getHandler());
    
        if (!requiredRoles && !requiredPermission) return true; // Public route
    
        const { user } = context.switchToHttp().getRequest();
    
        if (requiredRoles && !requiredRoles.includes(user.role)) {
          throw new ForbiddenException(`Requires role: ${requiredRoles.join(' or ')}`);
        }
    
        if (requiredPermission && !this.hasPermission(user, requiredPermission)) {
          throw new ForbiddenException(`Requires permission: ${requiredPermission}`);
        }
    
        return true;
      }
    }
    
    // Usage Matrixon controller
    @Get('users')
    @Roles(Role.ADMIN)
    @RequirePermission('users:read')
    async listUsers() { ... }
    

    Role hierarchy:

    admin > manager > user > viewer > public
    
    Full Read/writeownorg Read/writeown
    RouteRole Rate LimitrequireAuthrequireMerchantFeature FlagValidation FunctionsCapabilities
    GET /api/auth/bankidadmin 10/min NoNoNoaccess
    GET /api/auth/bankid/callbackmanager 10/min No No Nostate cookieresources
    GET /api/auth/meuser No Yes NoNoresources
    POST /api/auth/logoutviewer NoYesNoNo
    POST /api/auth/refreshNoYesNoNo
    GET /api/transactionsNoYesNoNo
    POST /api/transactions/remittance10/minYesNoNovalidateAmount
    POST /api/transactions/qr-payment10/minYesNoNovalidateAmount
    GET /api/rates120/minNoNoNo
    POST /api/recipientsNoYesNoNovalidateName, country whitelist
    POST /api/merchants/registerNoYesNoNovalidateName, orgNumber
    GET /api/merchants/dashboardNoYesYesNoperiod whitelist
    GET /api/notificationsNoYesNonotifications
    PATCH /api/notificationsNoYesNonotificationsID format, max 100
    PATCH /api/settingsNoYesNoNocurrency/language whitelist
    POST /api/cards/[id]/physicalNoYesNophysicalCardsaddress min 10 chars
    POST /api/cards/[id]/pinNoYesNocardPinvalidatePIN
    GET/PUT /api/cards/[id]/limitsNoYesNospendingLimitslimitType whitelistRead-only

    6.

    2.6 ErrorValidation SpikeMiddleware

    Detection

    Implemented inLibrary: src/lib/alerts.tsclass-validator + class-transformer asOR azod

    middleware-adjacent
    // concern:Global validation pipe (NestJS)
    app.useGlobalPipes(new ValidationPipe({
      whitelist: true,              // Strip unknown properties
      forbidNonWhitelisted: true,  // Throw if unknown properties present
      transform: true,              // Auto-transform to DTO types
      transformOptions: {
        enableImplicitConversion: true,
      },
    }));
    

    Sanitization rules:

    • EveryAll HTTPstring 5xxinputs: responsetrim triggerswhitespace
    • HTML content: sanitize with trackError()DOMPurify / sanitize-html (calledstrip indangerous jsonError() middleware for 500 errors)tags)
    • RollingSQL 1-minuteparameters: windowalways ofuse errorparameterized timestampsqueries maintained(ORM in-memoryhandles this)
    • WhenFile countuploads: >validate 5MIME intype 60by secondsmagic bytes sends(not criticaljust Slack alert to #drop-ops
    • 10-minute cooldown per alert title prevents spamextension)

    Limitation:Validation error format:

    {
      "error": {
        "code": "VALIDATION_ERROR",
        "message": "Request validation failed",
        "details": [
          { "field": "email", "message": "must be an email" },
          { "field": "name", "message": "must be longer than 2 characters" }
        ]
      }
    }
    

    Performance impact: < 1ms for typical DTOs


    2.7 Rate Limiting Middleware

    Library: {{@nestjs/throttler | express-rate-limit | rate-limiter-flexible}} Storage: {{Redis}} (shared across all replicas)

    Algorithms:

    AlgorithmLibraryBest For
    Fixed windowexpress-rate-limitSimple, low overhead
    Sliding windowrate-limiter-flexibleAccurate, no burst at window edge
    Token bucketrate-limiter-flexibleBursty traffic patterns

    Selected algorithm: {{Sliding window}}

    Configuration:

    const rateLimiter = new RateLimiterRedis({
      storeClient: redisClient,
      keyPrefix: 'rl',
      points: 1000,         // Number of points
      duration: 60,         // Per 60 seconds
      blockDuration: 60,    // Block for 60s after exceeded
    });
    
    // Per-route overrides
    const loginLimiter = new RateLimiterRedis({
      points: 5,
      duration: 900,        // 15 minutes
      blockDuration: 900,
    });
    

    Key strategy: {{IP address | User ID (if authenticated) | IP + User ID}}

    Performance impact: ~1-2ms (Redis round-trip)


    2.8 Audit Logging Middleware

    // interceptors/audit.interceptor.ts
    @Injectable()
    export class AuditInterceptor implements NestInterceptor {
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        const request = context.switchToHttp().getRequest();
        const { method, path, user, requestId } = request;
    
        // Only audit mutating operations
        if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
          this.auditService.log({
            requestId,
            userId: user?.id,
            method,
            path,
            body: this.sanitizeBody(request.body), // Strip PII
            timestamp: new Date().toISOString(),
          });
        }
    
        return next.handle();
      }
    
      private sanitizeBody(body: Record<string, unknown>) {
        const REDACTED_FIELDS = ['password', 'token', 'creditCard', 'ssn'];
        return Object.fromEntries(
          Object.entries(body).map(([key, value]) =>
            REDACTED_FIELDS.includes(key) ? [key, '[REDACTED]'] : [key, value]
          )
        );
      }
    }
    

    What IS logged:

    • User ID, request ID, timestamp, method, path
    • Response status code, duration
    • Mutation summaries (what changed, not full values)

    What is NEVER logged:

    • Passwords, tokens, API keys
    • Payment card data
    • Full PII fields (log field names but not values for sensitive fields)

    Audit log retention: {{1 year}} (compliance requirement: {{GDPR / SOC2 / internal}})


    2.9 Error counterHandling isMiddleware

    in-memory only
    // filters/global-exception.filter.ts
    @Catch()
    export class GlobalExceptionFilter implements ExceptionFilter {
      catch(exception: unknown, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const response = ctx.getResponse<Response>();
        const request = ctx.getRequest<Request>();
    
        let status = 500;
        let code = 'INTERNAL_ERROR';
        let message = 'An unexpected error occurred';
        let details: unknown[] = [];
    
        if (exception instanceof HttpException) {
          status = exception.getStatus();
          const exceptionResponse = exception.getResponse() as any;
          code = exceptionResponse.code ?? 'HTTP_ERROR';
          message = exceptionResponse.message ?? exception.message;
          details = exceptionResponse.details ?? [];
        }
    
        // Log 5xx errors (not 4xxresetsthose are client errors)
        if (status >= 500) {
          this.logger.error('Unhandled exception', { exception, requestId: request.requestId });
          this.sentryService.captureException(exception);
        }
    
        response.status(status).json({
          error: {
            code,
            message,
            details,
            requestId: request.requestId,
            timestamp: new Date().toISOString(),
          },
        });
      }
    }
    

    3. Custom Middleware Development Guide

    Template for new middleware:

    // middleware/{{name}}.middleware.ts
    import { Injectable, NestMiddleware } from '@nestjs/common';
    import { Request, Response, NextFunction } from 'express';
    
    @Injectable()
    export class {{Name}}Middleware implements NestMiddleware {
      use(req: Request, res: Response, next: NextFunction): void {
        // 1. Extract needed data from request
        // 2. Perform validation/enrichment/logging
        // 3. Set data on applicationrequest restart.object Redis-backedif counterneeded
        planned// 4. Call next() or throw HttpException
    
        next();
      }
    }
    

    Requirements for v1.0.new middleware:

    •  Handles errors without crashing the process
    •  Calls next() exactly once (or throws)
    •  Does not block async operations without async/await
    •  Performance impact documented
    •  Unit tests covering happy path + error path

    4. Middleware Ordering & Dependencies

    Request → [1] → [2] → [3] → [4] → [5] → [6] → [7] → [8] → [9] → Handler
    
    [1] CORS          — No dependencies
    [2] Security      — No dependencies
    [3] Request ID    — Must be before Logger (Logger reads requestId)
    [4] Rate Limiter  — Must be after Request ID (uses requestId for key)
    [5] Logger        — Must be after Request ID
    [6] Auth          — Must be after Logger (Logger should log auth failures)
    [7] Authorization — MUST be after Auth (requires user on request)
    [8] Validation    — MUST be after Auth (DTOs may reference user context)
    [9] Audit Logger  — MUST be after Auth (logs user ID)
    

    NEVER reorder middleware without reviewing this dependency chain.


    Related5. DocumentsPerformance Impact Per Middleware

    MiddlewareAvg Latency AddedP99 Latency AddedNotes
    CORS0.05ms0.1msHeader injection only
    Security Headers (Helmet)0.1ms0.2msHeader injection only
    Request ID0.1ms0.2msID generation
    Rate Limiter1.5ms5msRedis round-trip
    Request Logger0.5ms1msAsync log write
    Authentication (JWT)3ms8msCrypto + optional Redis
    Authorization0.5ms1msIn-memory role check
    Validation0.8ms2msSchema parsing
    Audit Logger0.5ms1msAsync DB write
    Total~7ms~18msMiddleware overhead

    Target: middleware overhead < 10ms P50, < 25ms P99.


    6. Testing Strategy for Middleware

    // Example unit test for Auth middleware
    describe('JwtGuard', () => {
      it('should attach user to request on valid token', async () => {
        const token = generateTestToken({ sub: 'usr_123', role: 'user' });
        const mockRequest = { headers: { authorization: `Bearer ${token}` } };
        const result = await guard.canActivate(createMockContext(mockRequest));
        expect(result).toBe(true);
        expect(mockRequest.user).toMatchObject({ id: 'usr_123', role: 'user' });
      });
    
      it('should throw UnauthorizedException on expired token', async () => {
        const expiredToken = generateExpiredToken();
        const mockRequest = { headers: { authorization: `Bearer ${expiredToken}` } };
        await expect(guard.canActivate(createMockContext(mockRequest)))
          .rejects.toThrow('TOKEN_EXPIRED');
      });
    });
    

    Test coverage requirements:


    7. Configuration Options Per Middleware

    MiddlewareEnvironment VariableDefaultDescription
    CORSCORS_ORIGINSlocalhost:3000Comma-separated allowed origins
    Rate LimitRATE_LIMIT_POINTS1000Requests per window
    Rate LimitRATE_LIMIT_DURATION60Window size in seconds
    Rate Limit (auth)AUTH_RATE_LIMIT_POINTS5Login attempts per window
    Audit LogAUDIT_LOG_RETENTION_DAYS365How long to keep audit records
    Request BodyMAX_REQUEST_BODY_SIZE1mbMax request body size

    Approval

    Bašić
    Role Name Date Signature
    Author Platform Architect (AI) 2026-02-23
    ReviewerBackend Lead
    ApproverSecurity Lead Alem
    Tech Lead