Skip to content

feat: add CAT/BAT (NUT-21/22) authentication support#94

Draft
4xvgal wants to merge 2 commits intocashubtc:masterfrom
4xvgal:feat/auth-wallet
Draft

feat: add CAT/BAT (NUT-21/22) authentication support#94
4xvgal wants to merge 2 commits intocashubtc:masterfrom
4xvgal:feat/auth-wallet

Conversation

@4xvgal
Copy link

@4xvgal 4xvgal commented Feb 12, 2026

Description

  • This PR adds NUT-21/22 authentication support (CAT/BAT) to CoCo, enabling OIDC-based mint authentication via
    mgr.auth.*. It integrates cashu-ts AuthManager which handles the full CAT/BAT lifecycle (CAT
    storage/refresh, BAT auto-minting/pool management, DLEQ validation, per-endpoint NUT-21 vs NUT-22 detection).

    The CAT flow is fully functional end-to-end. BAT (Blind Auth Token) flow requires another repository to handle it

    Key changes:

    • Added AuthApi class that orchestrates per-mint cashu-ts AuthManager + OIDCAuth lifecycle
    • Added AuthSession model, AuthSessionRepository interface, and MemoryAuthSessionRepository
    • Added AuthSessionService for session CRUD with expiration validation and event emission
    • Extended MintAdapter with setAuthProvider/clearAuthProvider to inject AuthProvider into cashu-ts Mint constructor
      (auto-includes CAT/BAT headers in all HTTP requests)
    • Exposed as mgr.auth following existing Service -> API -> Manager pattern
    • Added domain errors: AuthSessionError, AuthSessionExpiredError
    • Added events: auth-session:updated, auth-session:deleted, auth-session:expired

Notes to Reviewers

CAT Flow (working now): After mgr.auth.startDeviceAuth(mintUrl) or mgr.auth.login(mintUrl, tokens), the AuthProvider
is injected into MintAdapter. All subsequent Mint HTTP requests automatically include the Clear-auth header.
Session tokens are persisted via AuthSessionService and can be restored on app restart with
mgr.auth.restore(mintUrl).

BAT Flow (blocked): cashu-ts AuthManager handles BAT minting/pooling internally, but two things are
needed:

  1. Multi-unit support merged (unit:'auth' keyset) — currently a separate PR
  2. WalletService.buildWallet() needs to pass authProvider to its Mint instances

No Breaking Changes: All new APIs are additive. Existing code continues to work without modification.

No Database Migration Required: MemoryAuthSessionRepository is provided for tests. Platform storage adapters
(sqlite3, indexeddb, expo-sqlite) are tracked as future work.

API Changes

New: mgr.auth (AuthApi)

  • mgr.auth.startDeviceAuth(mintUrl) — OIDC Device Code Flow; returns { verification_uri, user_code, poll(), cancel()
    }
  • mgr.auth.login(mintUrl, tokens) — Manual login with externally obtained OIDC tokens
  • mgr.auth.restore(mintUrl) — Restore persisted session on app restart; returns boolean
  • mgr.auth.logout(mintUrl) — Delete session and disconnect AuthProvider
  • mgr.auth.getSession(mintUrl) — Get valid (non-expired) session; throws if missing/expired
  • mgr.auth.hasSession(mintUrl) — Check if session exists
  • mgr.auth.getAuthProvider(mintUrl) — Get cashu-ts AuthProvider for advanced use

New: MintAdapter

  • MintAdapter.setAuthProvider(mintUrl, provider) — Inject AuthProvider into Mint constructor
  • MintAdapter.clearAuthProvider(mintUrl) — Remove AuthProvider and invalidate cached Mint

New: AuthSessionService

  • AuthSessionService.saveSession(mintUrl, tokens) — Persist OIDC tokens as AuthSession
  • AuthSessionService.getValidSession(mintUrl) — Get session with expiration check
  • AuthSessionService.deleteSession(mintUrl) — Delete session
  • AuthSessionService.hasSession(mintUrl) — Check existence

Suggested CHANGELOG Updates

ADDED

  • AuthSession model — { mintUrl, accessToken, refreshToken?, expiresAt, scope? }
  • AuthSessionRepository interface — getSession, saveSession, deleteSession
  • MemoryAuthSessionRepository — In-memory implementation for tests
  • AuthApi class — NUT-21/22 authentication orchestrator (mgr.auth.*)
  • AuthApi.startDeviceAuth(mintUrl) — OIDC Device Code Flow
  • AuthApi.login(mintUrl, tokens) — Manual OIDC token login
  • AuthApi.restore(mintUrl) — Session recovery on app restart
  • AuthApi.logout(mintUrl) — Session cleanup
  • AuthApi.getSession(mintUrl) / hasSession(mintUrl) — Session queries
  • AuthApi.getAuthProvider(mintUrl) — Access cashu-ts AuthProvider
  • AuthSessionService — Session CRUD + expiration validation + event emission
  • AuthSessionError / AuthSessionExpiredError — Domain error classes
  • auth-session:updated / auth-session:deleted / auth-session:expired — EventBus events
  • MintAdapter.setAuthProvider() / clearAuthProvider() — AuthProvider injection
  • 9 unit tests for AuthApi
  • Integration test for OIDC Device Code Flow

MODIFIED

  • Manager — Added readonly auth: AuthApi, wired in buildCoreServices / buildApis
  • MintAdapter.getCashuMint() — Now passes authProvider to new Mint() constructor
  • RepositoriesBase — Added authSessionRepository field
  • AuthSessionService — Fixed Logger import (@nestjs/common → @core/logging)

Remaining Work (tracked in FEATURE_TODO.md)

  • Storage adapters: sqlite3, indexeddb, expo-sqlite
  • BAT persistence: AuthManager.exportPool() / importPool()
  • WalletService authProvider wiring (depends on multi-unit merge)
  • React wrapper hooks: useAuthSession(), useBatPool()

Test Coverage

Unit tests (9 new AuthApi tests, 441 total pass)
bun test packages/core/test/unit/AuthApi.test.ts

Integration test (requires mint with NUT-21/22 enabled + manual OIDC authorization)
MINT_URL=http://localhost:8085 bun test packages/core/test/integration/auth-session.test.ts --timeout 300000

I use customized oidc server nostr-oidc-ts for testing instead of KeyCloak
Which is pass cashu-ts AuthWallet device code flow test.

  authentication. Adds `mgr.auth.*` API following existing Service -> API
  -> Manager pattern.

New components:
  - AuthApi: orchestrates per-mint AuthManager + OIDCAuth lifecycle
    - startDeviceAuth(): OIDC Device Code Flow
    - login(): manual token injection
    - restore(): session recovery on app restart
    - logout(): cleanup session + AuthProvider
    - getSession() / hasSession() / getAuthProvider()
  - AuthSession model + AuthSessionRepository interface
  - AuthSessionService: session CRUD + expiration validation
  - MemoryAuthSessionRepository: in-memory impl for tests
  - MintAdapter: setAuthProvider/clearAuthProvider with cache invalidation
  - Domain errors: AuthSessionError, AuthSessionExpiredError
  - Events: auth-session:updated, auth-session:deleted, auth-session:expired

CAT flow is fully functional — AuthProvider is injected into Mint
  constructor.

BAT flow (NUT-22) requires multi-unit support (unit:'auth' keyset) and WalletService authProvider wiring - tracked as future work.

Tests: 9 unit tests (AuthApi) + integration test (Device Code FLow)
@changeset-bot
Copy link

changeset-bot bot commented Feb 12, 2026

⚠️ No Changeset found

Latest commit: 765ed69

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@4xvgal 4xvgal changed the title feat: add CAT/BAT (NUT-21/22) authentication suppor feat: add CAT/BAT (NUT-21/22) authentication support Feb 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant