Skip to content

Task 16: Existing Flow Audit — Steps 1-5 Deep Review

Overview

Deep audit of the already-built SuperAdmin → Org Admin → Employee flow to verify integration quality before POC demo. Found 4 critical issues and 2 expected-mock items.

Audit Date: 2026-04-15


Audit Scope

Verified 5 steps that were reported as "done with real Auth0":

  1. SuperAdmin creates organization
  2. KYB approval
  3. Create Company Admin user
  4. Company Admin login
  5. Company Admin adds employees

Methodology: Traced code paths in service/controller files, checked for mocks, placeholders, hardcoded values, missing error handling, security gaps.


Results Summary

Step Component Real/Mock Grade
1 Create Organization Real Auth0 + Real DB ⚠️ Works but has KYB bypass
2 KYB Approval Real DB ✅ Works correctly
3 Create Company Admin Real Auth0 (with rollback) ✅ Works correctly
4 Company Admin Login Real Auth0 JWKS ❌ Security issues
5 Add Employees Real DB, org-scoped ✅ Works correctly

Overall: Functional for demo, but 4 critical issues must be fixed before production. 2 of them should be fixed before POC demo (security visibility).


🔴 Critical Issue #1: KYB Auto-Approval Bypass

Severity: HIGH (compliance / business logic) File: app/backend/src/admin/services/organization.service.ts (lines 230-235)

The Problem

When SuperAdmin creates a new organization, the code automatically sets:

savedOrganization.kybStatus = KybStatus.APPROVED;
savedOrganization.status = OrganizationStatus.ISSUER_ENABLED;

This bypasses the entire KYB approval workflow. Organizations become fully operational the moment they're created — no KYB review, no manual approval, no compliance check.

Why This Matters

  • The v3.0 requirements explicitly document a multi-step KYB flow (DRAFT → KYB_PENDING → KYB_APPROVED → ISSUER_ENABLED)
  • Client expects this workflow to work as designed
  • Later in the demo, you'd need to show SuperAdmin approving KYB — but it's already approved

Fix

Option A — Simple (recommended for POC): Remove auto-approval in createOrganization(). Let organizations start in DRAFT state. Use the existing updateKybStatus() endpoint to transition to APPROVED after review.

Option B — Keep for demo convenience: Add an env flag POC_AUTO_APPROVE_KYB=true to toggle this behavior. Production will set it to false.

Effort

30 minutes


🔴 Critical Issue #2: Hardcoded Auth0 Management API Credentials

Severity: CRITICAL (security) File: app/backend/src/auth/auth.service.ts (lines 21-22) Status (2026-08-14): Code path fixed, credential itself still NOT rotated — see Task 32 items #5/#6 and Still-Open #1.

The Problem

clientId: '<redacted — see note below>',
clientSecret: '<redacted — see note below>',

Real Auth0 Management API credentials hardcoded in source code. These are committed to git history.

2026-08-14 update: the raw values that were originally in this section have been redacted here — this file is tracked in git and there is no reason to keep repeating a live secret in plaintext across multiple committed files. The underlying credential pair is the same one Task 32 found still loaded (under the wrong env var name, AUTH0_MGMT_CLIENT_ID) in .env.development, now renamed to AUTH0_MGMT_CLIENT_ID_COMPROMISED_ROTATE_ME as a loud warning marker. auth.service.ts no longer hardcodes it (reads AUTH0_MANAGEMENT_CLIENT_ID/_SECRET from config), so Critical Issue #2's code defect is fixed. The credential itself has still not been rotated in the Auth0 dashboard — that is the one remaining action here, and it's external (Auth0 tenant settings), not a code change.

Why This Matters

  • Credentials in git are effectively public (anyone with repo access has them)
  • Auth0 Management API gives full control over users, orgs, everything
  • If the repo is shared with Curo, Velocity, or any third party, they have production-level access
  • Client's v3.0 security requirements explicitly call out "zero trust architecture" — hardcoded secrets violate this

Fix

~~Replace with environment variables~~ — done, auth.service.ts now reads AUTH0_MANAGEMENT_CLIENT_ID/AUTH0_MANAGEMENT_CLIENT_SECRET via ConfigService. Remaining: 1. Rotate the credential pair in the Auth0 dashboard (it's compromised by being in git history — still valid/live until this happens) 2. Update the new rotated pair in .env/.env.example/deployed secrets managers, under the correct AUTH0_MANAGEMENT_CLIENT_* names

Effort

~~1 hour~~ — remaining work is just the Auth0-dashboard rotation + updating the env value, ~15 minutes.


🔴 Critical Issue #3: JWT Audience Validation Disabled

Severity: CRITICAL (security) File: app/backend/src/auth/strategies/jwt.strategy.ts (line 113)

The Problem

audience: false, // Temporarily disable audience validation for debugging

Audience validation is completely disabled. Any JWT from any Auth0 application in the same tenant will be accepted.

Why This Matters

  • If you have multiple apps in the same Auth0 tenant (which is normal), tokens are interchangeable
  • An attacker with a token from ANY Auth0 app can authenticate to this API
  • Client's security review will flag this immediately
  • Fix is a one-line change

Fix

audience: this.configService.get<string>('AUTH0_AUDIENCE'),

Remove the debugging comment. Ensure AUTH0_AUDIENCE is set in .env.

Effort

15 minutes


🔴 Critical Issue #4: No Organization Context Validation in JWT

Severity: HIGH (authorization / multi-tenancy) File: app/backend/src/auth/strategies/jwt.strategy.ts (validate() method)

The Problem

When a user logs in, their organization context comes from the database, NOT from the JWT. This means: - If user.organizationId is modified in DB, they immediately access a different org - No cryptographic binding between login token and organization - Org Admin of Company A could theoretically access Company B if DB is modified

Why This Matters

  • v3.0 requires "data segregation between different companies"
  • Multi-tenancy security depends on the JWT carrying tenant context
  • Auth0 organizations feature provides org_id in tokens — we're not using it

Fix

In jwt.strategy.ts::validate():

const orgIdFromToken = payload.org_id || payload['https://curo-rec/org_id'];

// Platform admins might not have org context — that's fine
if (user.role !== UserRole.PLATFORM_ADMIN) {
  if (!orgIdFromToken) {
    throw new UnauthorizedException('Token missing organization context');
  }
  if (user.organizationId !== orgIdFromToken) {
    throw new UnauthorizedException('Organization mismatch');
  }
}

Also ensure Auth0 is configured to include org_id in tokens when user logs in via organization.

Effort

2 hours (code + Auth0 config + testing)


⚠️ Expected Mock #1: VNF Credential Generation

Severity: None (expected) File: app/backend/src/issuer/services/manual-credential.service.ts (line 187)

Status

Uses MockVCLProvider to generate mock VNF credentials. This is the main scope of Task 09 — replacing with real CIH API calls.

Fix

Already planned in Task 09. No additional action needed here.


⚠️ Expected Mock #2: POC Mode Tenant Creation

Severity: None (expected) File: app/backend/src/admin/services/velocity-registrar.service.ts (lines 182-184)

Status

If VNF_ENVIRONMENT=poc-mock or NODE_ENV=development, creates mock tenant. This is the main scope of Task 09.

Fix

Already planned in Task 09. No additional action needed here.


What Works Well ✅

Step 3 — Create Company Admin (Excellent Implementation)

superadmin-organization.service.ts::createOrgAdmin() has: - Real Auth0 user creation - Adds user to Auth0 organization (multi-tenant) - Rollback logic if any step fails - Validates KYB approval status before allowing - Sends proper invitation emails

This is production-quality code.

Step 5 — Employee Management (Excellent Implementation)

employee.service.ts has: - Every query scoped by orgId - Role guards (@Roles(ORG_ADMIN, CANDIDATE, PLATFORM_ADMIN)) - Conflict checks on employee IDs - Proper validation - Bulk operations with transaction safety

This is production-quality code.

Auth0 Integration (Real, Not Mocked)

  • auth0-user.service.ts — real Auth0 Management API calls
  • auth0-organization.service.ts — real Auth0 organizations
  • JWT verification uses real JWKS endpoint
  • Session management with expiration
  • MFA support in place

Priority Matrix — What to Fix When

Must Fix Before POC Demo

# Issue Why Effort
2 Hardcoded Auth0 credentials Client will see the code 1 hr
3 JWT audience validation disabled Easy one-line fix, high visibility 15 min

Total: ~1.25 hours. Do these alongside CIH integration.

# Issue Why Effort
1 KYB auto-approval bypass The demo story tells about KYB approval 30 min

Fix Before Production (Drop 1 Beta)

# Issue Why Effort
4 JWT organization context validation Security hardening 2 hrs

Already Planned

# Issue Plan
5 Mock VNF credentials Task 09
6 Mock tenant creation Task 09

Revised POC Effort Estimate

Task Effort
Fix hardcoded Auth0 credentials (#2) 1 hr
Fix JWT audience validation (#3) 15 min
Fix KYB auto-approval (#1) 30 min
Task 09 — CIH Integration 9 hrs
GAP-01 — QR code on claim page 1 hr
End-to-end testing 2 hrs
Total POC Work ~14 hours / ~2 days

Recommendations

  1. Fix Issue #2 immediately — rotate the exposed Auth0 credentials in Auth0 dashboard and remove from code. This is a security incident regardless of POC status.

  2. Fix Issues #1 and #3 alongside CIH integration — minimal additional effort, high impact on demo quality.

  3. Document Issue #4 for Drop 1 Beta security hardening task list.

  4. Consider pre-demo checklist:

  5. All 4 critical issues fixed
  6. End-to-end test: SuperAdmin creates org → approve KYB → create admin → admin logs in → issues credential via real CIH → employee claims via QR code
  7. Test with 2 different orgs to verify multi-tenancy isolation

References