Remote Eaze
Security

Security Overview

Defense-in-depth security architecture covering authentication, license enforcement, HTTP hardening, audit logging, and encryption.

This document outlines the security architecture of the Remote Eaze DFA solution. The system employs defense-in-depth principles with cryptographic protection, multi-layered access control, and comprehensive audit capabilities.

Request Lifecycle Security Chain

Every authenticated request passes through multiple security gates in order:

Request
  → populateUser()          Resolve session from cookie (Better Auth)
  → requireAuth()           Validate user exists + license guard
  → passwordGuard()         Enforce password change / expiry
  → requirePermission()     RBAC check (4-layer engine)
  → Route handler           Business logic with tenant scoping

Each gate is fail-closed — if any layer throws, the request is denied and an audit event is logged.

Authentication (Better Auth)

Authentication is handled by Better Auth with Fastify integration. A catch-all route at /api/auth/* proxies requests to Better Auth's handler.

Invite-Only Registration

Public signup is blocked in production. Users are created exclusively through admin invitation:

  1. Admin calls POST /api/v1/users/ with user details and role assignment
  2. System validates: email uniqueness, MX records on email domain, role and branch exist within tenant
  3. A cryptographically secure temporary password is generated using crypto.randomBytes + Fisher-Yates shuffle, meeting the tenant's password policy with a 4-character entropy buffer
  4. Better Auth creates the account internally (auth.api.signUpEmail())
  5. mustChangePassword is set to true; welcome email sent with temp credentials
  6. Temporary password is never returned in production API responses (dev only)

Session Management

  • Sessions stored in remoteEaze_userSession (Prisma) with id, token, expiresAt, ipAddress, userAgent
  • Cookie-based session management via Better Auth
  • Custom session plugin enriches every session response with roleSlug and roleName (avoids extra API calls for frontend RBAC)
  • Users can list, revoke specific, revoke all, or revoke other sessions
  • Sessions cascade-delete when a user is deleted

Self-Service Restrictions

Users calling /api/auth/update-user cannot modify security-critical fields:

roleId, dataAccessScope, branchId, tenantId, userType, costCentre, userDeptUnit, reportingToId, staffNumber, branchRestrict

These require admin action via PUT /api/v1/users/:id.

Password Security

Tenant-Specific Policies

Each tenant configures their own password policy via their license record:

SettingDefaultRange
passwordMinLength88–128
passwordMinNumber10–10
passwordMinUppercase10–10
passwordMinLowercase10–10
passwordMinSpecialChar10–10
passwordChangeInterval90 days0–365
inactivityTimeout600 seconds60–86,400

The SYSTEM tenant uses a stricter default: passwordMinLength: 12.

Password Validation Points

WhenPolicy Source
Sign-up (dev only)Tenant policy from request body tenantId
Change passwordTenant policy resolved from session cookie
Reset passwordSYSTEM policy (user is unauthenticated)
User invitationTenant policy for password generation

Password Guard (Global Hook)

An onRequest hook on every request enforces password change requirements:

  1. If mustChangePassword === true → 403 with reason PASSWORD_CHANGE_REQUIRED
  2. If passwordLastChangedAt + passwordChangeInterval < now → sets mustChangePassword = true, returns 403 with reason PASSWORD_EXPIRED

Exempt routes: /api/auth/sign-in/*, /api/auth/get-session, /api/auth/change-password, /api/auth/sign-out, /health, /documentation

SYSTEM tenant users are fully exempt from the password guard.

License Guard

On every authenticated request (except exempt routes), the guard calls fastify.licenses.getLicense(tenantId) which performs:

  1. Block list check — Redis key license:blocked:{id}. If set (tampering detected), immediate 403 LICENSE_TAMPERED
  2. Cache check — Redis key license:data:{id}. If cached, validate expiry in tenant's timezone
  3. DB fetch — Load license, check isActive and soft-delete status
  4. Integrity verification — Recompute HMAC-SHA256 signature and compare with crypto.timingSafeEqual. On mismatch: set 5-minute Redis block, throw 403
  5. Expiry check — Timezone-aware end-of-day evaluation. Throw 402 LICENSE_EXPIRED if past
  6. Cache result — Dynamic TTL: min(3600, max(60, secondsUntilExpiry))

Exempt from license checks: SYSTEM tenant users, /api/auth/* routes, health/docs routes, and a tenant admin viewing their own license.

For the full license data model and API, see License Security.

Permission Engine

The system implements a 4-layer hybrid permission model combining RBAC with conditional rules, amount thresholds, and multi-level approval workflows.

Decision Hierarchy

LayerNameBehavior
L0System Admin BypassroleSlug === "SYSTEM_ADMIN" → allow immediately
L1Base Permission MatrixRole × Entity × Action lookup. If not allowed → hard deny (fast fail)
L2bVALIDATION RulesTenant-wide hard blocks (e.g., no transactions on holidays). If match → deny
L2bPERMISSION RulesRole-specific overrides with conditions. First match by priority wins. Can change requiredLevels
L2aAmount ThresholdsCurrency + amount range lookup. Strict mode: no matching range → deny
FallbackLayer 1 resultIf no L2 rules matched, return L1 result with base requiredLevel

Fail-Closed Policy

If Redis or the database is unreachable during permission evaluation, the system denies access rather than allowing it through. All denials are logged as HIGH sensitivity audit events.

Redis-Cached Permissions

Permissions are cached per role at permissions:{tenantId}:{roleId} with 1-hour TTL. All users sharing a role share one cache entry. Cache is invalidated on any mutation to the role's matrix, thresholds, or rules.

For the full permission system documentation, see Permissions & Maker-Checker.

HTTP Security Headers

@fastify/helmet (default configuration) sets the following headers:

HeaderValuePurpose
Content-Security-Policydefault-src 'self'; ...Restricts resource sources to same origin
Strict-Transport-Securitymax-age=31536000; includeSubDomainsForces HTTPS for 1 year
Cross-Origin-Opener-Policysame-originIsolates browsing context (Spectre mitigations)
Cross-Origin-Resource-Policysame-originPrevents cross-origin resource loading
X-Content-Type-OptionsnosniffPrevents MIME-sniffing
X-Frame-OptionsSAMEORIGINPrevents clickjacking
Referrer-Policyno-referrerStrips referrer data
X-DNS-Prefetch-ControloffDisables DNS prefetching
X-Download-OptionsnoopenPrevents IE download execution
X-Permitted-Cross-Domain-PoliciesnoneRestricts Flash/PDF cross-domain
X-XSS-Protection0Disables legacy XSS filter

Rate Limiting

@fastify/rate-limit is configured at the application layer:

  • Window: 1 minute
  • Max Requests: 100 per IP
  • Exempt: Localhost (127.0.0.1) for development

CORS

@fastify/cors configuration:

{
  origin: env.NODE_ENV === "production"
    ? env.FRONTEND_URL                                    // Strict production domain
    : ["http://localhost:5173", "http://127.0.0.1:5173"], // Vite dev server
  credentials: true,   // Required for session cookies
  methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
}

Audit Logging

All security-relevant events are logged through the activity log system with structured metadata, sensitivity classification, and tags.

Authentication Events

Every tracked auth event (sign-in, sign-out, change-password, reset-password, change-email, session operations) is logged with:

  • Actor identification (userId, name, role, branch — resolved from DB even for failed attempts on known emails)
  • Sensitivity: HIGH for PII operations and failures, MEDIUM for standard auth ops
  • changeBefore/changeAfter diffs for data-modifying operations
  • Email included in metadata only when HIGH sensitivity or failure

Permission Denials

Blocked permission checks are logged with HIGH sensitivity, full context:

  • Denied action and entity type
  • Actor role, tenant, and branch
  • Denial reason and which layer denied
  • Tags: security, authorization, access_denied

License Violations

License guard failures are logged with HIGH sensitivity:

  • LICENSE_TAMPERED → tags: security, tampering, integrity_violation
  • LICENSE_EXPIRED → tags: security, expiry, access_denied
  • LICENSE_INACTIVE → tags: security, disabled, access_denied

Tiered Guarantees

TierFailure ResponseUse Case
SYNCRequest aborted (500)Money movement, auth changes, license violations
QUEUEBackground retry (BullMQ)Updates, settings changes
ASYNCFire-and-forgetViews, searches, reports

SYNC-tier failures prevent un-audited security events from completing.

For the full activity logging architecture, see Activity Logs.

Data Protection and Encryption

PII Sanitization Pipeline

All audit logs pass through a multi-layer pipeline before storage:

  1. Redaction (always) — Secrets permanently replaced with [REDACTED] (passwords, tokens, API keys, PINs, CVVs)
  2. Encryption (HIGH sensitivity) — PII encrypted with AES-256-GCM (SSN, national IDs, email, phone, address, DOB, IBAN, account numbers)
  3. Truncation — Large binary data truncated to 20 chars

AES-256-GCM Encryption

  • Key derivation: scrypt using ENCRYPTION_KEY + ENCRYPTION_SALT from environment
  • Format: ENC:v1:IV:AuthTag:Ciphertext
  • Decryption audit: Every decrypt operation is logged with admin ID and reason

Key Management

ENCRYPTION_KEY=<32-byte-string>           # AES-256-GCM key for PII protection
ENCRYPTION_SALT=<16-byte-string>          # scrypt key derivation salt
LICENSE_SECRET_KEY=<32+-char-string>      # HMAC-SHA256 secret for license integrity
BETTER_AUTH_SECRET=<32+-char-string>      # Better Auth session secret

Key Rotation Warning

Changing encryption keys without migration renders existing encrypted data unrecoverable. Key rotation requires a coordinated migration script with dual-key support.

Request Tracing

Every request carries identifiers that flow through all security layers:

  • Request ID — Application-level tracking (X-Request-ID)
  • Trace ID — Distributed tracing for security workflows
  • Session ID — Authentication context correlation
  • Tenant ID — Isolation boundary for incident scoping

On this page