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":
- SuperAdmin creates organization
- KYB approval
- Create Company Admin user
- Company Admin login
- 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 toAUTH0_MGMT_CLIENT_ID_COMPROMISED_ROTATE_MEas a loud warning marker.auth.service.tsno longer hardcodes it (readsAUTH0_MANAGEMENT_CLIENT_ID/_SECRETfrom 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_idin 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 callsauth0-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.
Fix During POC (recommended)¶
| # | 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¶
-
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.
-
Fix Issues #1 and #3 alongside CIH integration — minimal additional effort, high impact on demo quality.
-
Document Issue #4 for Drop 1 Beta security hardening task list.
-
Consider pre-demo checklist:
- All 4 critical issues fixed
- End-to-end test: SuperAdmin creates org → approve KYB → create admin → admin logs in → issues credential via real CIH → employee claims via QR code
- Test with 2 different orgs to verify multi-tenancy isolation