Skip to content

feat: admin wallet allowlist guard - #110

Merged
EmeditWeb merged 1 commit into
StepFi-app:mainfrom
KingFRANKHOOD:feat/admin-wallet-allowlist
Jul 27, 2026
Merged

feat: admin wallet allowlist guard#110
EmeditWeb merged 1 commit into
StepFi-app:mainfrom
KingFRANKHOOD:feat/admin-wallet-allowlist

Conversation

@KingFRANKHOOD

@KingFRANKHOOD KingFRANKHOOD commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Close #97
Adds an admin access control system based on a Stellar wallet allowlist. Any wallet that completes the normal nonce-and-signature flow currently has access to the full admin audit log via GET /admin/audit-logs. This PR gates all admin endpoints behind an AdminGuard that checks the authenticated wallet against a configurable allowlist.

Closes the admin access control gap identified in the codebase.

Changes

  • New env var: \ADMIN_WALLETS\ — comma-separated Stellar public keys. Validated at startup; invalid entries cause immediate failure.
  • New guard: \AdminGuard\ (src/auth/guards/admin.guard.ts) — resolves the authenticated wallet from
    eq.user\ and checks membership in the allowlist.
  • Applied to: \AuditController\ (src/modules/admin/audit.controller.ts) — now requires both \JwtAuthGuard\ and \AdminGuard.
  • Startup validation: \�alidateAdminWallets()\ called from \AppModule.onModuleInit\ — fails fast with a clear message on invalid Stellar addresses.
  • Tests: 8 unit tests covering allowlisted wallet (200), non-allowlisted wallet (403), empty/unset allowlist (denies all), unauthenticated request, and response body non-disclosure.

Design Decisions

  • Admin is NOT a role. Roles (\sponsor/ \�endor/ \mentor) are self-selected and permanent. Adding \�dmin\ to \USER_ROLES\ would be a privilege escalation — anyone could claim it. Admin status derives from the allowlist only.
  • Empty = deny all. An unset or empty \ADMIN_WALLETS\ denies all admin access rather than granting it. This is the safe default for a config mistake.
  • Generic 403 body. The response does not leak the allowlist contents or size.
  • Follows existing patterns. Guard structure mirrors \RolesGuard. Stellar address validation uses \StrKey.isValidEd25519PublicKey\ (same as auth.service.ts). Logging uses \Logger\ from @nestjs/common.

Controller Audit

Audited all 16 controllers for endpoints assuming administrator access without enforcement:

Controller Route prefix Result
AuditController /admin Fixed — AdminGuard added
VendorsController /vendors All self-service (vendor manages own data)
LoansController /loans All user-facing (quote, create, repay)
SponsorsController /sponsors Self-service sponsor operations
LiquidityController /liquidity Role-gated (sponsor only)
UsersController /users Self-service profile; role-setting is USER_ROLES-validated
VouchingController /vouching Mentor/sponsor operations
TransactionsController /transactions User transaction history
NotificationsController /notifications User notifications
ReputationController /reputation Public reputation reads
HealthController /health Public health checks
MetricsController /metrics Public Prometheus metrics
AuthController /auth Public auth endpoints
StellarTomlController /.well-known Public stellar.toml
IndexerController /indexer Internal health check

No other controllers have endpoints that assume administrator access. Vendor approval, protocol pause, and parameter governance (mentioned in the issue) do not yet exist as endpoints — this PR establishes the admin guard infrastructure for when they are built.

Testing


  • pm run build\ — zero TypeScript errors

  • pm run lint\ — zero lint errors

  • pm test\ — 325/325 tests pass (29 suites), no test count decrease
  • 8 new tests for AdminGuard

Acceptance Criteria

  • GET /admin/* returns 403 for authenticated wallet not on allowlist
  • GET /admin/* returns 401 for unauthenticated request (JwtAuthGuard handles this)
  • Unset or empty ADMIN_WALLETS denies all admin access
  • Startup fails with clear message if any allowlist entry is not a valid Stellar address
  • No admin address hardcoded in any service file
  • Admin is not added to USER_ROLES and cannot be self-assigned
  • 403 response body does not disclose allowlist contents or size
  • All state-changing operations validated and safe against invalid input

Files Changed

File Action
src/config/env.ts Added \�alidateAdminWallets()\ and \getAdminWallets()\
src/auth/guards/admin.guard.ts New — AdminGuard implementation
src/modules/admin/audit.controller.ts Added AdminGuard to @UseGuards
src/app.module.ts Added OnModuleInit to call validateAdminWallets at startup
.env.example Documented ADMIN_WALLETS variable
test/unit/modules/auth/admin.guard.spec.ts New — 8 unit tests

- Add ADMIN_WALLETS env var with comma-separated Stellar addresses
- Validate entries at startup (fail-fast on invalid addresses)
- Create AdminGuard that checks wallet against allowlist
- Apply AdminGuard to audit controller (GET /admin/audit-logs)
- Empty/unset ADMIN_WALLETS denies all admin access by design
- Generic 403 response does not leak allowlist contents
- Add unit tests (8 tests) for all guard scenarios
- Audit all controllers: no other endpoints require admin protection
@KingFRANKHOOD
KingFRANKHOOD requested a review from EmeditWeb as a code owner July 27, 2026 12:15

@EmeditWeb EmeditWeb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work, kudos

@EmeditWeb
EmeditWeb merged commit 5f23a83 into StepFi-app:main Jul 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security: protect admin endpoints with an admin allowlist guard

2 participants