@pafi-dev/issuer 0.39.2 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-client/index.cjs +65 -79
- package/dist/auth-client/index.cjs.map +1 -1
- package/dist/auth-client/index.js +2 -2
- package/dist/{chunk-7VEYSL2C.js → chunk-2Z3M2KQG.js} +69 -80
- package/dist/{chunk-7VEYSL2C.js.map → chunk-2Z3M2KQG.js.map} +1 -1
- package/dist/chunk-7QVYU63E.js +7 -0
- package/dist/{chunk-QLNGNH4A.js → chunk-RNQQYJIB.js} +23 -7
- package/dist/{chunk-QLNGNH4A.js.map → chunk-RNQQYJIB.js.map} +1 -1
- package/dist/direct-auth/index.cjs +363 -195
- package/dist/direct-auth/index.cjs.map +1 -1
- package/dist/direct-auth/index.js +304 -132
- package/dist/direct-auth/index.js.map +1 -1
- package/dist/http/index.cjs +14 -1
- package/dist/http/index.cjs.map +1 -1
- package/dist/http/index.js +2 -2
- package/dist/index.cjs +1093 -1482
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -156
- package/dist/index.d.ts +33 -156
- package/dist/index.js +1069 -1533
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +114 -50
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.js +106 -61
- package/dist/nestjs/index.js.map +1 -1
- package/dist/wallet-auth/index.cjs +11 -5
- package/dist/wallet-auth/index.cjs.map +1 -1
- package/dist/wallet-auth/index.js +13 -6
- package/dist/wallet-auth/index.js.map +1 -1
- package/package.json +5 -3
- package/dist/chunk-BRKEJJFQ.js +0 -17
- /package/dist/{chunk-BRKEJJFQ.js.map → chunk-7QVYU63E.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/direct-auth/pafi-direct-auth.module.ts","../../src/direct-auth/services/pafi-auth-client.provider.ts","../../src/direct-auth/pafi-direct-auth.module-options.ts","../../src/direct-auth/services/pafi-session-verifier.service.ts","../../src/direct-auth/services/pafi-direct-auth.service.ts","../../src/direct-auth/interfaces/user-store.interface.ts","../../src/direct-auth/interfaces/session-token-minter.interface.ts","../../src/direct-auth/pafi-direct-auth.controller.ts","../../src/direct-auth/pafi-direct-auth.dto.ts"],"sourcesContent":["import { Module, type DynamicModule } from \"@nestjs/common\";\n\nimport { PafiAuthClientProvider } from \"./services/pafi-auth-client.provider\";\nimport { PafiSessionVerifierService } from \"./services/pafi-session-verifier.service\";\nimport { PafiDirectAuthService } from \"./services/pafi-direct-auth.service\";\nimport { PafiDirectAuthController } from \"./pafi-direct-auth.controller\";\nimport {\n PAFI_DIRECT_AUTH_MODULE_OPTIONS,\n type PafiDirectAuthModuleOptions,\n} from \"./pafi-direct-auth.module-options\";\nimport { USER_STORE } from \"./interfaces/user-store.interface\";\nimport { SESSION_TOKEN_MINTER } from \"./interfaces/session-token-minter.interface\";\n\n/**\n * NestJS module wiring the direct-auth surface (`/auth/v2/*`\n * controller, orchestrator service, gateway client, session\n * verifier).\n *\n * Issuer backends mount this once at the root module with their\n * gateway credentials + their `IUserStore` / `ISessionTokenMinter`\n * implementations. Critically, the adapter classes are instantiated\n * **inside this dynamic module** (not the host) so the SDK's\n * orchestrator service can resolve them — meaning any module the\n * adapter classes depend on (e.g. TypeORM features for repositories)\n * MUST be passed via `imports`:\n *\n * ```ts\n * @Module({\n * imports: [\n * ConfigModule.forRoot({ isGlobal: true }),\n * PafiDirectAuthModule.forRoot({\n * imports: [TypeOrmModule.forFeature([UserEntity])],\n * gatewayUrl: process.env.PAFI_GATEWAY_URL!,\n * issuerId: process.env.PAFI_GATEWAY_ISSUER_ID!,\n * clientId: process.env.PAFI_GATEWAY_CLIENT_ID!,\n * clientPrivateJwk: JSON.parse(\n * process.env.PAFI_GATEWAY_CLIENT_PRIVATE_JWK_JSON!,\n * ) as JWK & { kid: string },\n * userStore: Gg56UserStore,\n * sessionTokenMinter: Gg56SessionMinter,\n * }),\n * ],\n * })\n * export class AuthModule {}\n * ```\n *\n * The adapter classes do NOT need to be listed in the host module's\n * `providers:` array — `PafiDirectAuthModule` registers them via\n * `useClass` so they live in this dynamic module's DI scope. Anything\n * they `@Inject` (`Repository<UserEntity>`, `ConfigService`,\n * `@Inject(REQUEST)`, etc.) must reach that scope via:\n * - `options.imports` for module-scoped providers (e.g. TypeORM features)\n * - The host's global module set for global providers (e.g.\n * `ConfigModule.forRoot({ isGlobal: true })` makes ConfigService\n * globally visible — no `imports:` entry needed)\n *\n * For ConfigService-based async setup, use `forRootAsync()` (TODO).\n */\n@Module({})\nexport class PafiDirectAuthModule {\n static forRoot(options: PafiDirectAuthModuleOptions): DynamicModule {\n return {\n module: PafiDirectAuthModule,\n imports: options.imports ?? [],\n controllers: [PafiDirectAuthController],\n providers: [\n {\n provide: PAFI_DIRECT_AUTH_MODULE_OPTIONS,\n useValue: options,\n },\n // Register adapter classes IN THIS MODULE'S SCOPE. NestJS then\n // resolves their constructor deps from imports + global\n // providers — which is exactly what the issuer expected to\n // happen via `useExisting`, but `useExisting` does NOT cross\n // module boundaries (it's an alias lookup within the module's\n // own provider registry, not a host-tree walk).\n options.userStore,\n options.sessionTokenMinter,\n {\n provide: USER_STORE,\n useExisting: options.userStore,\n },\n {\n provide: SESSION_TOKEN_MINTER,\n useExisting: options.sessionTokenMinter,\n },\n PafiAuthClientProvider,\n PafiSessionVerifierService,\n PafiDirectAuthService,\n ],\n exports: [\n PafiAuthClientProvider,\n PafiSessionVerifierService,\n PafiDirectAuthService,\n ],\n };\n }\n}\n","import { Injectable, Inject, type OnModuleInit } from \"@nestjs/common\";\nimport type { JWK } from \"jose\";\n\nimport { PafiAuthClient } from \"../../auth-client/pafi-auth-client\";\nimport {\n PAFI_DIRECT_AUTH_MODULE_OPTIONS,\n type PafiDirectAuthModuleOptions,\n} from \"../pafi-direct-auth.module-options\";\n\n/**\n * Thin DI wrapper around {@link PafiAuthClient}. Constructs a single\n * client at boot using the issuer's gateway credentials and exposes it\n * to services that need to call the direct-auth endpoints.\n *\n * One instance per process — the SDK client is stateless and the\n * client_assertion is signed on every call (no warm-up needed).\n *\n * Config comes from `PafiDirectAuthModule.forRoot(options)` (see\n * `pafi-direct-auth.module.ts`). The host app supplies `gatewayUrl`,\n * `issuerId`, `clientId`, and `clientPrivateJwk` either as literal\n * values OR as a factory that resolves them from NestJS ConfigService\n * (`forRootAsync` variant).\n */\n@Injectable()\nexport class PafiAuthClientProvider implements OnModuleInit {\n private _client!: PafiAuthClient;\n\n constructor(\n @Inject(PAFI_DIRECT_AUTH_MODULE_OPTIONS)\n private readonly options: PafiDirectAuthModuleOptions,\n ) {}\n\n onModuleInit(): void {\n const jwk = this.options.clientPrivateJwk as JWK;\n if (!jwk.kid) {\n throw new Error(\n \"PafiDirectAuthModule: clientPrivateJwk.kid is required — gateway uses kid for key lookup\",\n );\n }\n this._client = new PafiAuthClient({\n gatewayUrl: this.options.gatewayUrl,\n issuerId: this.options.issuerId,\n clientId: this.options.clientId,\n clientPrivateJwk: jwk as JWK & { kid: string },\n });\n }\n\n get client(): PafiAuthClient {\n return this._client;\n }\n}\n","import type { JWK } from \"jose\";\nimport type {\n DynamicModule,\n ForwardReference,\n Type,\n} from \"@nestjs/common\";\n\nimport type { IUserStore } from \"./interfaces/user-store.interface\";\nimport type { ISessionTokenMinter } from \"./interfaces/session-token-minter.interface\";\n\n/**\n * Options consumed by `PafiDirectAuthModule.forRoot(options)`. The\n * host (issuer backend) supplies:\n *\n * - Gateway credentials (URL, issuer_id, client_id, private JWK)\n * - Issuer-specific implementations of IUserStore + ISessionTokenMinter\n * - Any NestJS modules the adapter classes depend on (e.g.\n * `TypeOrmModule.forFeature([UserEntity])` so an adapter can\n * `@InjectRepository(UserEntity)`).\n *\n * Typically read from `ConfigService` in the issuer backend. Use\n * `forRootAsync()` (planned) when config needs `inject: [ConfigService]`.\n */\nexport interface PafiDirectAuthModuleOptions {\n /** Base URL of the PAFI gateway (e.g. `https://id-dev.pacificfinance.org`). */\n gatewayUrl: string;\n /** Issuer identifier registered with the gateway (e.g. `gg56`). */\n issuerId: string;\n /**\n * Gateway client_id assigned at issuer onboarding. Also acts as the\n * `iss`/`sub` of the client_assertion JWT (RFC 7523 §3).\n */\n clientId: string;\n /**\n * Private JWK the issuer uses to sign client_assertion. MUST include\n * `kid` — gateway looks up the matching public JWK by kid.\n */\n clientPrivateJwk: JWK & { kid: string };\n /**\n * Issuer-implemented user-row upsert. SDK calls this on every\n * successful direct-auth flow to ensure the local users row exists\n * keyed by canonical_id. See `IUserStore` for contract details.\n */\n userStore: Type<IUserStore>;\n /**\n * Issuer-implemented session-token minter. SDK calls this once per\n * successful direct-auth flow to wrap the gateway-returned canonical\n * + verified email into the issuer's own session JWT. See\n * `ISessionTokenMinter` for contract details.\n */\n sessionTokenMinter: Type<ISessionTokenMinter>;\n /**\n * NestJS modules whose providers the adapter classes need to see at\n * DI-resolution time. The SDK registers `userStore` +\n * `sessionTokenMinter` inside this dynamic module's own scope (via\n * `useClass`), so any constructor dependencies they declare —\n * `Repository<UserEntity>` (TypeORM), custom services, etc. — must\n * reach that scope through this `imports:` array. Global providers\n * (e.g. `ConfigService` when ConfigModule is `isGlobal: true`) are\n * visible automatically and do NOT need to be listed here.\n *\n * @example\n * ```ts\n * PafiDirectAuthModule.forRoot({\n * imports: [TypeOrmModule.forFeature([UserEntity])],\n * userStore: Gg56UserStore, // @InjectRepository(UserEntity) ↑\n * sessionTokenMinter: Gg56SessionMinter, // ConfigService (global)\n * ...\n * })\n * ```\n */\n imports?: Array<\n Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference\n >;\n}\n\n/** DI token for the resolved options. Use with `@Inject(PAFI_DIRECT_AUTH_MODULE_OPTIONS)`. */\nexport const PAFI_DIRECT_AUTH_MODULE_OPTIONS = Symbol(\n \"PAFI_DIRECT_AUTH_MODULE_OPTIONS\",\n);\n","import {\n Injectable,\n Inject,\n UnauthorizedException,\n} from \"@nestjs/common\";\nimport { createRemoteJWKSet, jwtVerify, type JWTPayload } from \"jose\";\n\nimport {\n PAFI_DIRECT_AUTH_MODULE_OPTIONS,\n type PafiDirectAuthModuleOptions,\n} from \"../pafi-direct-auth.module-options\";\n\nexport interface PafiSessionClaims {\n sub: string;\n scope: \"pafi-session\";\n /** Verified attribute the gateway used to derive canonical_id. */\n verifiedAttribute?: { type: string; valueHash?: string };\n /** Issuer that originated the auth (must equal our PAFI_GATEWAY_ISSUER_ID). */\n issuerId?: string;\n exp: number;\n iat: number;\n raw: JWTPayload;\n}\n\n/**\n * Verifies the long-lived `pafi_session_token` returned by the\n * gateway's direct-auth endpoints. Defense-in-depth: the gateway just\n * minted this token, so the chance of forgery is near zero — BUT\n * verifying it locally guarantees we never trust an attacker-supplied\n * \"fake gateway response\" that bypassed the network call (e.g. a\n * compromised inbound proxy injecting an arbitrary JSON body). One\n * audit line for a millisecond of crypto work.\n *\n * Uses the gateway's published JWKS (`/.well-known/jwks.json`) — same\n * key set Privy uses when verifying the short-lived `pafi_jwt`. The\n * remote JWKS is cached + auto-refreshed by jose.\n */\n@Injectable()\nexport class PafiSessionVerifierService {\n private readonly jwks: ReturnType<typeof createRemoteJWKSet>;\n private readonly expectedIssuer: string;\n\n constructor(\n @Inject(PAFI_DIRECT_AUTH_MODULE_OPTIONS)\n options: PafiDirectAuthModuleOptions,\n ) {\n this.jwks = createRemoteJWKSet(\n new URL(`${options.gatewayUrl}/.well-known/jwks.json`),\n );\n this.expectedIssuer = options.gatewayUrl;\n }\n\n async verify(token: string): Promise<PafiSessionClaims> {\n let payload: JWTPayload;\n try {\n ({ payload } = await jwtVerify(token, this.jwks, {\n issuer: this.expectedIssuer,\n }));\n } catch (err) {\n throw new UnauthorizedException(\n `Invalid pafi_session_token: ${(err as Error).message}`,\n );\n }\n\n if (payload.scope !== \"pafi-session\") {\n throw new UnauthorizedException(\n `pafi_session_token has wrong scope: ${String(payload.scope)}`,\n );\n }\n if (typeof payload.sub !== \"string\") {\n throw new UnauthorizedException(\"pafi_session_token missing sub\");\n }\n if (typeof payload.exp !== \"number\" || typeof payload.iat !== \"number\") {\n throw new UnauthorizedException(\"pafi_session_token missing iat/exp\");\n }\n\n const verifiedAttribute = parseVerifiedAttribute(\n (payload as { verified_attribute?: unknown }).verified_attribute,\n );\n return {\n sub: payload.sub,\n scope: \"pafi-session\",\n verifiedAttribute,\n issuerId:\n typeof (payload as { issuer_id?: unknown }).issuer_id === \"string\"\n ? ((payload as { issuer_id?: string }).issuer_id as string)\n : undefined,\n exp: payload.exp,\n iat: payload.iat,\n raw: payload,\n };\n }\n}\n\nfunction parseVerifiedAttribute(\n raw: unknown,\n): PafiSessionClaims[\"verifiedAttribute\"] {\n if (!raw || typeof raw !== \"object\") return undefined;\n const obj = raw as { type?: unknown; value_hash?: unknown };\n if (typeof obj.type !== \"string\") return undefined;\n return {\n type: obj.type,\n valueHash:\n typeof obj.value_hash === \"string\" ? obj.value_hash : undefined,\n };\n}\n","import { Injectable, Inject, Logger } from \"@nestjs/common\";\n\nimport { PafiAuthClientProvider } from \"./pafi-auth-client.provider\";\nimport { PafiSessionVerifierService } from \"./pafi-session-verifier.service\";\nimport {\n USER_STORE,\n type IUserStore,\n} from \"../interfaces/user-store.interface\";\nimport {\n SESSION_TOKEN_MINTER,\n type ISessionTokenMinter,\n} from \"../interfaces/session-token-minter.interface\";\n\nexport interface DirectAuthResult {\n sessionToken: string;\n sessionExpiresAt: string;\n pafiJwt: string;\n pafiSessionToken: string;\n canonicalId: string;\n isFirstLogin: boolean;\n verifiedEmail?: string;\n}\n\n/**\n * Orchestrates the gateway-owned auth flows for an issuer backend. The\n * gateway is the sole verifier (it ran the OTP check / verified the\n * Google id_token / exchanged the Kakao code); this service is a thin\n * adapter that:\n *\n * 1. Forwards the call via {@link PafiAuthClient} (RFC 7523\n * client_assertion signed by the issuer's private JWK)\n * 2. Verifies the returned `pafi_session_token` against the gateway's\n * JWKS (defense in depth — see {@link PafiSessionVerifierService})\n * 3. Upserts the local user row via {@link IUserStore} (issuer\n * implements; SDK doesn't know the DB schema)\n * 4. Mints an issuer-native session token via\n * {@link ISessionTokenMinter} (issuer implements; SDK doesn't see\n * the issuer's JWT_SECRET)\n *\n * The returned `sessionToken.sub` is the canonical_id — the same value\n * the gateway uses for the PAFI JWT's `sub`. This means downstream\n * services that need a stable user id can use it without caring which\n * auth method produced it.\n *\n * Extracted from per-issuer boilerplate code in 2026-06-30 SDK refactor.\n * Previously copy-pasted (~260 lines per issuer); now ~60 lines per\n * issuer (UserStore + SessionMinter + module wiring).\n */\n@Injectable()\nexport class PafiDirectAuthService {\n private readonly logger = new Logger(PafiDirectAuthService.name);\n\n constructor(\n private readonly clientProvider: PafiAuthClientProvider,\n private readonly sessionVerifier: PafiSessionVerifierService,\n @Inject(USER_STORE) private readonly userStore: IUserStore,\n @Inject(SESSION_TOKEN_MINTER)\n private readonly sessionTokenMinter: ISessionTokenMinter,\n ) {}\n\n // ── Email OTP ────────────────────────────────────────────────────\n\n async startEmail(args: {\n email: string;\n correlationId?: string;\n }): Promise<{ challengeId: string; expiresInSec: number }> {\n return this.clientProvider.client.startEmail({\n email: args.email,\n correlationId: args.correlationId,\n });\n }\n\n async verifyEmail(args: {\n challengeId: string;\n otpCode: string;\n correlationId?: string;\n }): Promise<DirectAuthResult> {\n const success = await this.clientProvider.client.verifyEmail({\n challengeId: args.challengeId,\n otpCode: args.otpCode,\n correlationId: args.correlationId,\n });\n await this.sessionVerifier.verify(success.pafiSessionToken);\n return this.finalize(success);\n }\n\n // ── Google ───────────────────────────────────────────────────────\n\n async exchangeGoogle(args: {\n idToken: string;\n correlationId?: string;\n }): Promise<DirectAuthResult> {\n const success = await this.clientProvider.client.exchangeGoogle({\n idToken: args.idToken,\n correlationId: args.correlationId,\n });\n await this.sessionVerifier.verify(success.pafiSessionToken);\n return this.finalize(success);\n }\n\n // ── Kakao ────────────────────────────────────────────────────────\n\n async exchangeKakao(args: {\n code: string;\n redirectUri?: string;\n correlationId?: string;\n }): Promise<DirectAuthResult> {\n const success = await this.clientProvider.client.exchangeKakao({\n code: args.code,\n redirectUri: args.redirectUri,\n correlationId: args.correlationId,\n });\n await this.sessionVerifier.verify(success.pafiSessionToken);\n return this.finalize(success);\n }\n\n // ── Internal: upsert user + mint issuer session token ───────────\n\n private async finalize(success: {\n canonicalId: string;\n pafiJwt: string;\n pafiSessionToken: string;\n isFirstLogin: boolean;\n verifiedEmail?: string;\n }): Promise<DirectAuthResult> {\n await this.userStore.upsertByCanonicalAndEmail({\n canonicalId: success.canonicalId,\n verifiedEmail: success.verifiedEmail,\n });\n\n const { token, expiresAt } = await this.sessionTokenMinter.mint({\n canonicalId: success.canonicalId,\n verifiedEmail: success.verifiedEmail,\n });\n\n return {\n sessionToken: token,\n sessionExpiresAt: expiresAt,\n pafiJwt: success.pafiJwt,\n pafiSessionToken: success.pafiSessionToken,\n canonicalId: success.canonicalId,\n isFirstLogin: success.isFirstLogin,\n ...(success.verifiedEmail ? { verifiedEmail: success.verifiedEmail } : {}),\n };\n }\n}\n","/**\n * Issuer-side abstraction for \"make sure the local user row exists,\n * keyed by the canonical_pafi_user_id the gateway just resolved.\"\n *\n * The SDK can't own this because each issuer has a different\n * `users` table schema (gg56 has `wallet_address`, lotteria doesn't,\n * future issuers might add `phone_number` / `kyc_status` / etc.).\n * Each consuming issuer backend implements `IUserStore` once and\n * passes the class to `PafiDirectAuthModule.forRoot({ userStore })`.\n *\n * Idempotency contract: callers invoke this on every successful\n * direct-auth flow (email OTP / Google / Kakao). Implementation MUST\n * be idempotent — re-calling with the same args is a no-op, NOT an\n * error.\n *\n * Lookup precedence (recommended): canonical_id first, fall back to\n * email. Same canonical across issuers = cross-issuer wallet merge.\n *\n * Race-safety: parallel requests for the same brand-new canonical\n * should be tolerated. The SDK assumes the underlying DB enforces a\n * unique constraint on canonical_id; on conflict, treat as \"row\n * already exists\" rather than re-throwing.\n *\n * @example\n * ```ts\n * @Injectable()\n * class Gg56UserStore implements IUserStore {\n * constructor(\n * @InjectRepository(UserEntity)\n * private readonly users: Repository<UserEntity>,\n * ) {}\n *\n * async upsertByCanonicalAndEmail(args: {\n * canonicalId: string;\n * verifiedEmail?: string;\n * }): Promise<void> {\n * const byCanonical = await this.users.findOne({\n * where: { canonicalId: args.canonicalId },\n * });\n * if (byCanonical) return;\n * // ... fall back to email, create row, race-handle, etc.\n * }\n * }\n * ```\n */\nexport interface IUserStore {\n upsertByCanonicalAndEmail(args: {\n /** canonical_pafi_user_id the gateway just minted. Always present. */\n canonicalId: string;\n /**\n * Verified email when the auth method exposed one (email OTP +\n * Google always; Kakao only if user shared email at consent).\n * Issuer may use to display profile, NOT for primary user lookup\n * (canonicalId is the stable id).\n */\n verifiedEmail?: string;\n }): Promise<void>;\n}\n\n/** DI token for IUserStore. Use with `@Inject(USER_STORE)`. */\nexport const USER_STORE = Symbol(\"USER_STORE\");\n","/**\n * Issuer-side abstraction for \"mint an issuer-native session token\n * the FE will Bearer-auth with for all subsequent /api calls.\"\n *\n * The SDK can't own this because:\n * - Each issuer signs with their own `JWT_SECRET` (HS256) OR with\n * their own RSA/ECDSA key — SDK shouldn't see secrets\n * - Token shape may differ (sub format, scope claims, extra claims\n * like role/tier specific to the issuer's app)\n * - Lifetime may differ (24h default, but some issuers want shorter)\n * - Existing JwtGuard / SessionTokenGuard in the issuer backend\n * expects a specific shape — SDK can't predict it\n *\n * The SDK orchestrator calls `mint()` once per successful direct-auth\n * flow, BEFORE returning the bundled response to the issuer's HTTP\n * controller. The minter is responsible for:\n * 1. Producing a token the issuer's existing guards accept\n * 2. Embedding `authAttribute: {type, value}` if the issuer's\n * /wallet/exchange-pafi-jwt refresh path needs it (most do — see\n * ISSUER_INTEGRATION_GUIDE.md §9 refresh path)\n * 3. Computing absolute expiry timestamp the FE can show\n *\n * @example\n * ```ts\n * @Injectable()\n * class Gg56SessionMinter implements ISessionTokenMinter {\n * private readonly secret: Uint8Array;\n * constructor(config: ConfigService) {\n * this.secret = new TextEncoder().encode(\n * config.getOrThrow('JWT_SECRET'),\n * );\n * }\n *\n * async mint(args: {\n * canonicalId: string;\n * verifiedEmail?: string;\n * }): Promise<{ token: string; expiresAt: string }> {\n * const now = Math.floor(Date.now() / 1000);\n * const exp = now + 86400;\n * const token = await new SignJWT({\n * userKey: args.canonicalId,\n * loginType: 'pafi-direct',\n * scope: 'pafi-session',\n * ...(args.verifiedEmail\n * ? { authAttribute: { type: 'email', value: args.verifiedEmail } }\n * : {}),\n * })\n * .setProtectedHeader({ alg: 'HS256', typ: 'JWT' })\n * .setSubject(args.canonicalId)\n * .setIssuedAt(now)\n * .setExpirationTime(exp)\n * .sign(this.secret);\n * return { token, expiresAt: new Date(exp * 1000).toISOString() };\n * }\n * }\n * ```\n */\nexport interface ISessionTokenMinter {\n mint(args: {\n /** canonical_pafi_user_id, use as `sub` so downstream identifies user uniformly. */\n canonicalId: string;\n /**\n * Verified email when available. Implementations SHOULD embed this\n * as `authAttribute: { type: 'email', value }` in the JWT body so\n * the legacy /wallet/exchange-pafi-jwt refresh path keeps deriving\n * the same canonical_id from the same attribute across refreshes.\n */\n verifiedEmail?: string;\n }): Promise<{\n /** The signed session token (any JWT shape — issuer guards must accept). */\n token: string;\n /** ISO 8601 absolute expiry timestamp. Returned to FE so it can show \"expires at X\". */\n expiresAt: string;\n }>;\n}\n\n/** DI token for ISessionTokenMinter. Use with `@Inject(SESSION_TOKEN_MINTER)`. */\nexport const SESSION_TOKEN_MINTER = Symbol(\"SESSION_TOKEN_MINTER\");\n","import {\n Body,\n Controller,\n HttpCode,\n HttpStatus,\n Post,\n} from \"@nestjs/common\";\nimport { ApiOkResponse, ApiOperation, ApiTags } from \"@nestjs/swagger\";\n\nimport {\n EmailStartRequestDto,\n EmailStartResponseDto,\n EmailVerifyRequestDto,\n GoogleExchangeRequestDto,\n KakaoExchangeRequestDto,\n PafiAuthSuccessDto,\n} from \"./pafi-direct-auth.dto\";\nimport { PafiDirectAuthService } from \"./services/pafi-direct-auth.service\";\n\n/**\n * `/auth/v2/*` — gateway-owned auth endpoints (2026-06-30 direct-auth\n * surface, shipped by `@pafi-dev/issuer/direct-auth`).\n *\n * Issuer backend no longer verifies OTP codes, OAuth id_tokens, or\n * holds OAuth client_secrets. All of that lives at the PAFI gateway.\n * These endpoints are thin proxies that forward user input via\n * {@link PafiDirectAuthService}, then upsert the local user row (via\n * the issuer-provided {@link IUserStore}) + mint an issuer-native\n * session token (via the issuer-provided {@link ISessionTokenMinter})\n * wrapping the gateway-assigned canonical_id.\n *\n * Mounted automatically by `PafiDirectAuthModule.forRoot(options)`.\n * Issuer apps that want to layer extra auth-error handling (e.g. a\n * custom `AuthErrorFilter`) can wrap the controller's routes via a\n * NestJS global filter — no per-route `@UseFilters` needed here.\n */\n@ApiTags(\"pafi-auth-v2\")\n@Controller(\"auth/v2\")\nexport class PafiDirectAuthController {\n constructor(private readonly directAuth: PafiDirectAuthService) {}\n\n @Post(\"email/start\")\n @HttpCode(HttpStatus.OK)\n @ApiOperation({\n summary: \"Step 1: ask gateway to send an OTP to the user email.\",\n description:\n \"Gateway generates the OTP, sends it via its configured email provider, and returns an opaque challenge_id. The FE echoes that challenge_id back on step 2 along with the code the user typed.\",\n })\n @ApiOkResponse({ type: EmailStartResponseDto })\n async startEmail(\n @Body() body: EmailStartRequestDto,\n ): Promise<EmailStartResponseDto> {\n const res = await this.directAuth.startEmail({ email: body.email });\n return {\n challengeId: res.challengeId,\n expiresInSec: res.expiresInSec,\n };\n }\n\n @Post(\"email/verify\")\n @HttpCode(HttpStatus.OK)\n @ApiOperation({\n summary: \"Step 2: submit the OTP to complete email sign-in.\",\n description:\n \"Gateway verifies the OTP, derives canonical_id from the verified email, and mints both a pafi_session_token (24h, gateway-signed) and pafi_jwt (60s, for Privy.loginWithCustomAuth). Issuer wraps these in a session token of its own (sub = canonical_id) so existing guards keep working.\",\n })\n @ApiOkResponse({ type: PafiAuthSuccessDto })\n async verifyEmail(\n @Body() body: EmailVerifyRequestDto,\n ): Promise<PafiAuthSuccessDto> {\n return this.directAuth.verifyEmail({\n challengeId: body.challengeId,\n otpCode: body.otpCode,\n });\n }\n\n @Post(\"google/exchange\")\n @HttpCode(HttpStatus.OK)\n @ApiOperation({\n summary: \"Sign in with Google.\",\n description:\n \"Hand the gateway a Google-issued id_token (FE obtains via Google Identity Services using PAFI's shared client_id). Gateway verifies signature + email_verified, derives canonical_id from the email, returns the same token bundle as /email/verify.\",\n })\n @ApiOkResponse({ type: PafiAuthSuccessDto })\n async exchangeGoogle(\n @Body() body: GoogleExchangeRequestDto,\n ): Promise<PafiAuthSuccessDto> {\n return this.directAuth.exchangeGoogle({ idToken: body.idToken });\n }\n\n @Post(\"kakao/exchange\")\n @HttpCode(HttpStatus.OK)\n @ApiOperation({\n summary: \"Sign in with Kakao.\",\n description:\n \"Hand the gateway the authorization code Kakao redirected back to the FE. Gateway exchanges with Kakao server-to-server (using PAFI-held client_secret), verifies the id_token, and returns the same token bundle as /email/verify. canonical_id derives from email when present, else from the Kakao sub.\",\n })\n @ApiOkResponse({ type: PafiAuthSuccessDto })\n async exchangeKakao(\n @Body() body: KakaoExchangeRequestDto,\n ): Promise<PafiAuthSuccessDto> {\n return this.directAuth.exchangeKakao({\n code: body.code,\n redirectUri: body.redirectUri,\n });\n }\n}\n","import { ApiProperty } from \"@nestjs/swagger\";\nimport {\n IsEmail,\n IsNotEmpty,\n IsOptional,\n IsString,\n IsUrl,\n Length,\n MaxLength,\n} from \"class-validator\";\n\n// ── Requests ────────────────────────────────────────────────────────\n\nexport class EmailStartRequestDto {\n @ApiProperty({ example: \"user1@example.com\" })\n @IsEmail()\n @MaxLength(320)\n email!: string;\n}\n\nexport class EmailVerifyRequestDto {\n @ApiProperty({\n description:\n \"Challenge id returned by POST /auth/v2/email/start. Opaque to the FE; echo verbatim.\",\n })\n @IsString()\n @IsNotEmpty()\n @MaxLength(128)\n challengeId!: string;\n\n @ApiProperty({ example: \"123456\" })\n @IsString()\n @Length(4, 10)\n otpCode!: string;\n}\n\nexport class GoogleExchangeRequestDto {\n @ApiProperty({\n description:\n \"Google-issued ID token (JWS). Obtain on FE via Google Identity Services using PAFI's Google OAuth client_id.\",\n })\n @IsString()\n @IsNotEmpty()\n @MaxLength(8192)\n idToken!: string;\n}\n\nexport class KakaoExchangeRequestDto {\n @ApiProperty({\n description:\n \"Authorization code returned by Kakao to the FE redirect URL.\",\n })\n @IsString()\n @IsNotEmpty()\n @MaxLength(2048)\n code!: string;\n\n @ApiProperty({\n description:\n \"Redirect URI the FE used when initiating the Kakao flow. Optional — gateway falls back to its own KAKAO_REDIRECT_URI env.\",\n required: false,\n })\n @IsOptional()\n @IsUrl({ require_tld: false, require_protocol: true })\n @MaxLength(2048)\n redirectUri?: string;\n}\n\n// ── Responses ───────────────────────────────────────────────────────\n\nexport class EmailStartResponseDto {\n @ApiProperty()\n challengeId!: string;\n\n @ApiProperty({ description: \"Seconds until the challenge expires.\" })\n expiresInSec!: number;\n}\n\nexport class PafiAuthSuccessDto {\n @ApiProperty({\n description:\n \"Issuer-native session token (typically HS256, minted by ISessionTokenMinter) — Bearer-auth for subsequent issuer API calls.\",\n })\n sessionToken!: string;\n\n @ApiProperty({ description: \"Issuer session token expiration (ISO 8601).\" })\n sessionExpiresAt!: string;\n\n @ApiProperty({\n description:\n \"Short-lived PAFI JWT (60s) — FE feeds verbatim to Privy.loginWithCustomAuth() to provision the embedded wallet.\",\n })\n pafiJwt!: string;\n\n @ApiProperty({\n description:\n \"Long-lived PAFI session token (24h) — opaque to FE; keep alongside sessionToken if you ever need to call the gateway directly.\",\n })\n pafiSessionToken!: string;\n\n @ApiProperty({ description: \"canonical_pafi_user_id assigned by the gateway.\" })\n canonicalId!: string;\n\n @ApiProperty({\n description: \"True the first time the user appears at the gateway.\",\n })\n isFirstLogin!: boolean;\n\n @ApiProperty({\n description:\n \"Verified email (when the auth method exposed one — email OTP and Google always; Kakao only if the user shared their email).\",\n required: false,\n })\n verifiedEmail?: string;\n}\n"],"mappings":"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","names":["Injectable","Inject","Injectable","Inject","Injectable","Inject","Injectable","Inject"]}
|
|
1
|
+
{"version":3,"sources":["../../src/direct-auth/pafi-direct-auth.module.ts","../../src/direct-auth/services/pafi-auth-client.provider.ts","../../src/direct-auth/pafi-direct-auth.module-options.ts","../../src/direct-auth/services/pafi-session-verifier.service.ts","../../src/direct-auth/services/pafi-direct-auth.service.ts","../../src/direct-auth/interfaces/user-store.interface.ts","../../src/direct-auth/interfaces/session-token-minter.interface.ts","../../src/direct-auth/pafi-direct-auth.controller.ts","../../src/direct-auth/pafi-direct-auth.dto.ts"],"sourcesContent":["import { Module, type DynamicModule } from \"@nestjs/common\";\n\nimport { PafiAuthClientProvider } from \"./services/pafi-auth-client.provider\";\nimport { PafiSessionVerifierService } from \"./services/pafi-session-verifier.service\";\nimport { PafiDirectAuthService } from \"./services/pafi-direct-auth.service\";\nimport { PafiDirectAuthController } from \"./pafi-direct-auth.controller\";\nimport {\n PAFI_DIRECT_AUTH_MODULE_OPTIONS,\n type PafiDirectAuthModuleOptions,\n} from \"./pafi-direct-auth.module-options\";\nimport { USER_STORE } from \"./interfaces/user-store.interface\";\nimport { SESSION_TOKEN_MINTER } from \"./interfaces/session-token-minter.interface\";\n\n/**\n * NestJS module wiring the direct-auth surface (`/auth/v2/*`\n * controller, orchestrator service, gateway client, session\n * verifier).\n *\n * Issuer backends mount this once at the root module with their\n * gateway credentials + their `IUserStore` / `ISessionTokenMinter`\n * implementations. Critically, the adapter classes are instantiated\n * **inside this dynamic module** (not the host) so the SDK's\n * orchestrator service can resolve them — meaning any module the\n * adapter classes depend on (e.g. TypeORM features for repositories)\n * MUST be passed via `imports`:\n *\n * ```ts\n * @Module({\n * imports: [\n * ConfigModule.forRoot({ isGlobal: true }),\n * PafiDirectAuthModule.forRoot({\n * imports: [TypeOrmModule.forFeature([UserEntity])],\n * gatewayUrl: process.env.PAFI_GATEWAY_URL!,\n * issuerId: process.env.PAFI_GATEWAY_ISSUER_ID!,\n * clientId: process.env.PAFI_GATEWAY_CLIENT_ID!,\n * clientPrivateJwk: JSON.parse(\n * process.env.PAFI_GATEWAY_CLIENT_PRIVATE_JWK_JSON!,\n * ) as JWK & { kid: string },\n * userStore: Gg56UserStore,\n * sessionTokenMinter: Gg56SessionMinter,\n * }),\n * ],\n * })\n * export class AuthModule {}\n * ```\n *\n * The adapter classes do NOT need to be listed in the host module's\n * `providers:` array — `PafiDirectAuthModule` registers them via\n * `useClass` so they live in this dynamic module's DI scope. Anything\n * they `@Inject` (`Repository<UserEntity>`, `ConfigService`,\n * `@Inject(REQUEST)`, etc.) must reach that scope via:\n * - `options.imports` for module-scoped providers (e.g. TypeORM features)\n * - The host's global module set for global providers (e.g.\n * `ConfigModule.forRoot({ isGlobal: true })` makes ConfigService\n * globally visible — no `imports:` entry needed)\n *\n * For ConfigService-based async setup, use `forRootAsync()` (TODO).\n */\n@Module({})\nexport class PafiDirectAuthModule {\n static forRoot(options: PafiDirectAuthModuleOptions): DynamicModule {\n return {\n module: PafiDirectAuthModule,\n imports: options.imports ?? [],\n controllers: [PafiDirectAuthController],\n providers: [\n {\n provide: PAFI_DIRECT_AUTH_MODULE_OPTIONS,\n useValue: options,\n },\n // Register adapter classes IN THIS MODULE'S SCOPE. NestJS then\n // resolves their constructor deps from imports + global\n // providers — which is exactly what the issuer expected to\n // happen via `useExisting`, but `useExisting` does NOT cross\n // module boundaries (it's an alias lookup within the module's\n // own provider registry, not a host-tree walk).\n options.userStore,\n options.sessionTokenMinter,\n {\n provide: USER_STORE,\n useExisting: options.userStore,\n },\n {\n provide: SESSION_TOKEN_MINTER,\n useExisting: options.sessionTokenMinter,\n },\n PafiAuthClientProvider,\n PafiSessionVerifierService,\n PafiDirectAuthService,\n ],\n exports: [\n PafiAuthClientProvider,\n PafiSessionVerifierService,\n PafiDirectAuthService,\n ],\n };\n }\n}\n","import { Injectable, Inject, type OnModuleInit } from \"@nestjs/common\";\nimport type { JWK } from \"jose\";\n\nimport { PafiAuthClient } from \"../../auth-client/pafi-auth-client\";\nimport {\n PAFI_DIRECT_AUTH_MODULE_OPTIONS,\n type PafiDirectAuthModuleOptions,\n} from \"../pafi-direct-auth.module-options\";\n\n/**\n * Thin DI wrapper around {@link PafiAuthClient}. Constructs a single\n * client at boot using the issuer's gateway credentials and exposes it\n * to services that need to call the direct-auth endpoints.\n *\n * One instance per process — the SDK client is stateless and the\n * client_assertion is signed on every call (no warm-up needed).\n *\n * Config comes from `PafiDirectAuthModule.forRoot(options)` (see\n * `pafi-direct-auth.module.ts`). The host app supplies `gatewayUrl`,\n * `issuerId`, `clientId`, and `clientPrivateJwk` either as literal\n * values OR as a factory that resolves them from NestJS ConfigService\n * (`forRootAsync` variant).\n */\n@Injectable()\nexport class PafiAuthClientProvider implements OnModuleInit {\n private _client!: PafiAuthClient;\n\n constructor(\n @Inject(PAFI_DIRECT_AUTH_MODULE_OPTIONS)\n private readonly options: PafiDirectAuthModuleOptions,\n ) {}\n\n onModuleInit(): void {\n const jwk = this.options.clientPrivateJwk as JWK;\n if (!jwk.kid) {\n throw new Error(\n \"PafiDirectAuthModule: clientPrivateJwk.kid is required — gateway uses kid for key lookup\",\n );\n }\n this._client = new PafiAuthClient({\n gatewayUrl: this.options.gatewayUrl,\n issuerId: this.options.issuerId,\n clientId: this.options.clientId,\n clientPrivateJwk: jwk as JWK & { kid: string },\n });\n }\n\n get client(): PafiAuthClient {\n return this._client;\n }\n}\n","import type { JWK } from \"jose\";\nimport type {\n DynamicModule,\n ForwardReference,\n Type,\n} from \"@nestjs/common\";\n\nimport type { IUserStore } from \"./interfaces/user-store.interface\";\nimport type { ISessionTokenMinter } from \"./interfaces/session-token-minter.interface\";\n\n/**\n * Options consumed by `PafiDirectAuthModule.forRoot(options)`. The\n * host (issuer backend) supplies:\n *\n * - Gateway credentials (URL, issuer_id, client_id, private JWK)\n * - Issuer-specific implementations of IUserStore + ISessionTokenMinter\n * - Any NestJS modules the adapter classes depend on (e.g.\n * `TypeOrmModule.forFeature([UserEntity])` so an adapter can\n * `@InjectRepository(UserEntity)`).\n *\n * Typically read from `ConfigService` in the issuer backend. Use\n * `forRootAsync()` (planned) when config needs `inject: [ConfigService]`.\n */\nexport interface PafiDirectAuthModuleOptions {\n /** Base URL of the PAFI gateway (e.g. `https://id-dev.pacificfinance.org`). */\n gatewayUrl: string;\n /** Issuer identifier registered with the gateway (e.g. `gg56`). */\n issuerId: string;\n /**\n * Gateway client_id assigned at issuer onboarding. Also acts as the\n * `iss`/`sub` of the client_assertion JWT (RFC 7523 §3).\n */\n clientId: string;\n /**\n * Private JWK the issuer uses to sign client_assertion. MUST include\n * `kid` — gateway looks up the matching public JWK by kid.\n */\n clientPrivateJwk: JWK & { kid: string };\n /**\n * Issuer-implemented user-row upsert. SDK calls this on every\n * successful direct-auth flow to ensure the local users row exists\n * keyed by canonical_id. See `IUserStore` for contract details.\n */\n userStore: Type<IUserStore>;\n /**\n * Issuer-implemented session-token minter. SDK calls this once per\n * successful direct-auth flow to wrap the gateway-returned canonical\n * + verified email into the issuer's own session JWT. See\n * `ISessionTokenMinter` for contract details.\n */\n sessionTokenMinter: Type<ISessionTokenMinter>;\n /**\n * NestJS modules whose providers the adapter classes need to see at\n * DI-resolution time. The SDK registers `userStore` +\n * `sessionTokenMinter` inside this dynamic module's own scope (via\n * `useClass`), so any constructor dependencies they declare —\n * `Repository<UserEntity>` (TypeORM), custom services, etc. — must\n * reach that scope through this `imports:` array. Global providers\n * (e.g. `ConfigService` when ConfigModule is `isGlobal: true`) are\n * visible automatically and do NOT need to be listed here.\n *\n * @example\n * ```ts\n * PafiDirectAuthModule.forRoot({\n * imports: [TypeOrmModule.forFeature([UserEntity])],\n * userStore: Gg56UserStore, // @InjectRepository(UserEntity) ↑\n * sessionTokenMinter: Gg56SessionMinter, // ConfigService (global)\n * ...\n * })\n * ```\n */\n imports?: Array<\n Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference\n >;\n}\n\n/** DI token for the resolved options. Use with `@Inject(PAFI_DIRECT_AUTH_MODULE_OPTIONS)`. */\nexport const PAFI_DIRECT_AUTH_MODULE_OPTIONS = Symbol(\n \"PAFI_DIRECT_AUTH_MODULE_OPTIONS\",\n);\n","import {\n Injectable,\n Inject,\n UnauthorizedException,\n} from \"@nestjs/common\";\nimport { createRemoteJWKSet, jwtVerify, type JWTPayload } from \"jose\";\n\nimport {\n PAFI_DIRECT_AUTH_MODULE_OPTIONS,\n type PafiDirectAuthModuleOptions,\n} from \"../pafi-direct-auth.module-options\";\n\nexport interface PafiSessionClaims {\n sub: string;\n scope: \"pafi-session\";\n /** Verified attribute the gateway used to derive canonical_id. */\n verifiedAttribute?: { type: string; valueHash?: string };\n /** Issuer that originated the auth (must equal our PAFI_GATEWAY_ISSUER_ID). */\n issuerId?: string;\n exp: number;\n iat: number;\n raw: JWTPayload;\n}\n\n/**\n * Verifies the long-lived `pafi_session_token` returned by the\n * gateway's direct-auth endpoints. Defense-in-depth: the gateway just\n * minted this token, so the chance of forgery is near zero — BUT\n * verifying it locally guarantees we never trust an attacker-supplied\n * \"fake gateway response\" that bypassed the network call (e.g. a\n * compromised inbound proxy injecting an arbitrary JSON body). One\n * audit line for a millisecond of crypto work.\n *\n * Uses the gateway's published JWKS (`/.well-known/jwks.json`) — same\n * key set Privy uses when verifying the short-lived `pafi_jwt`. The\n * remote JWKS is cached + auto-refreshed by jose.\n */\n@Injectable()\nexport class PafiSessionVerifierService {\n private readonly jwks: ReturnType<typeof createRemoteJWKSet>;\n private readonly expectedIssuer: string;\n\n constructor(\n @Inject(PAFI_DIRECT_AUTH_MODULE_OPTIONS)\n options: PafiDirectAuthModuleOptions,\n ) {\n this.jwks = createRemoteJWKSet(\n new URL(`${options.gatewayUrl}/.well-known/jwks.json`),\n );\n this.expectedIssuer = options.gatewayUrl;\n }\n\n async verify(token: string): Promise<PafiSessionClaims> {\n let payload: JWTPayload;\n try {\n ({ payload } = await jwtVerify(token, this.jwks, {\n issuer: this.expectedIssuer,\n }));\n } catch (err) {\n throw new UnauthorizedException(\n `Invalid pafi_session_token: ${(err as Error).message}`,\n );\n }\n\n if (payload.scope !== \"pafi-session\") {\n throw new UnauthorizedException(\n `pafi_session_token has wrong scope: ${String(payload.scope)}`,\n );\n }\n if (typeof payload.sub !== \"string\") {\n throw new UnauthorizedException(\"pafi_session_token missing sub\");\n }\n if (typeof payload.exp !== \"number\" || typeof payload.iat !== \"number\") {\n throw new UnauthorizedException(\"pafi_session_token missing iat/exp\");\n }\n\n const verifiedAttribute = parseVerifiedAttribute(\n (payload as { verified_attribute?: unknown }).verified_attribute,\n );\n return {\n sub: payload.sub,\n scope: \"pafi-session\",\n verifiedAttribute,\n issuerId:\n typeof (payload as { issuer_id?: unknown }).issuer_id === \"string\"\n ? ((payload as { issuer_id?: string }).issuer_id as string)\n : undefined,\n exp: payload.exp,\n iat: payload.iat,\n raw: payload,\n };\n }\n}\n\nfunction parseVerifiedAttribute(\n raw: unknown,\n): PafiSessionClaims[\"verifiedAttribute\"] {\n if (!raw || typeof raw !== \"object\") return undefined;\n const obj = raw as { type?: unknown; value_hash?: unknown };\n if (typeof obj.type !== \"string\") return undefined;\n return {\n type: obj.type,\n valueHash:\n typeof obj.value_hash === \"string\" ? obj.value_hash : undefined,\n };\n}\n","import { Injectable, Inject, Logger } from \"@nestjs/common\";\n\nimport { PafiAuthClientProvider } from \"./pafi-auth-client.provider\";\nimport { PafiSessionVerifierService } from \"./pafi-session-verifier.service\";\nimport {\n USER_STORE,\n type IUserStore,\n} from \"../interfaces/user-store.interface\";\nimport {\n SESSION_TOKEN_MINTER,\n type ISessionTokenMinter,\n} from \"../interfaces/session-token-minter.interface\";\n\nexport interface DirectAuthResult {\n sessionToken: string;\n sessionExpiresAt: string;\n pafiJwt: string;\n pafiSessionToken: string;\n canonicalId: string;\n isFirstLogin: boolean;\n verifiedEmail?: string;\n}\n\n/**\n * Orchestrates the gateway-owned auth flows for an issuer backend. The\n * gateway is the sole verifier (it ran the OTP check / verified the\n * Google id_token / exchanged the Kakao code); this service is a thin\n * adapter that:\n *\n * 1. Forwards the call via {@link PafiAuthClient} (RFC 7523\n * client_assertion signed by the issuer's private JWK)\n * 2. Verifies the returned `pafi_session_token` against the gateway's\n * JWKS (defense in depth — see {@link PafiSessionVerifierService})\n * 3. Upserts the local user row via {@link IUserStore} (issuer\n * implements; SDK doesn't know the DB schema)\n * 4. Mints an issuer-native session token via\n * {@link ISessionTokenMinter} (issuer implements; SDK doesn't see\n * the issuer's JWT_SECRET)\n *\n * The returned `sessionToken.sub` is the canonical_id — the same value\n * the gateway uses for the PAFI JWT's `sub`. This means downstream\n * services that need a stable user id can use it without caring which\n * auth method produced it.\n *\n * Extracted from per-issuer boilerplate code in 2026-06-30 SDK refactor.\n * Previously copy-pasted (~260 lines per issuer); now ~60 lines per\n * issuer (UserStore + SessionMinter + module wiring).\n */\n@Injectable()\nexport class PafiDirectAuthService {\n private readonly logger = new Logger(PafiDirectAuthService.name);\n\n constructor(\n private readonly clientProvider: PafiAuthClientProvider,\n private readonly sessionVerifier: PafiSessionVerifierService,\n @Inject(USER_STORE) private readonly userStore: IUserStore,\n @Inject(SESSION_TOKEN_MINTER)\n private readonly sessionTokenMinter: ISessionTokenMinter,\n ) {}\n\n // ── Email OTP ────────────────────────────────────────────────────\n\n async startEmail(args: {\n email: string;\n correlationId?: string;\n }): Promise<{ challengeId: string; expiresInSec: number }> {\n return this.clientProvider.client.startEmail({\n email: args.email,\n correlationId: args.correlationId,\n });\n }\n\n async verifyEmail(args: {\n challengeId: string;\n otpCode: string;\n correlationId?: string;\n }): Promise<DirectAuthResult> {\n const success = await this.clientProvider.client.verifyEmail({\n challengeId: args.challengeId,\n otpCode: args.otpCode,\n correlationId: args.correlationId,\n });\n await this.sessionVerifier.verify(success.pafiSessionToken);\n return this.finalize(success);\n }\n\n // ── Google ───────────────────────────────────────────────────────\n\n async exchangeGoogle(args: {\n idToken: string;\n correlationId?: string;\n }): Promise<DirectAuthResult> {\n const success = await this.clientProvider.client.exchangeGoogle({\n idToken: args.idToken,\n correlationId: args.correlationId,\n });\n await this.sessionVerifier.verify(success.pafiSessionToken);\n return this.finalize(success);\n }\n\n // ── Kakao ────────────────────────────────────────────────────────\n\n async exchangeKakao(args: {\n code: string;\n redirectUri?: string;\n correlationId?: string;\n }): Promise<DirectAuthResult> {\n const success = await this.clientProvider.client.exchangeKakao({\n code: args.code,\n redirectUri: args.redirectUri,\n correlationId: args.correlationId,\n });\n await this.sessionVerifier.verify(success.pafiSessionToken);\n return this.finalize(success);\n }\n\n // ── Internal: upsert user + mint issuer session token ───────────\n\n private async finalize(success: {\n canonicalId: string;\n pafiJwt: string;\n pafiSessionToken: string;\n isFirstLogin: boolean;\n verifiedEmail?: string;\n }): Promise<DirectAuthResult> {\n await this.userStore.upsertByCanonicalAndEmail({\n canonicalId: success.canonicalId,\n verifiedEmail: success.verifiedEmail,\n });\n\n const { token, expiresAt } = await this.sessionTokenMinter.mint({\n canonicalId: success.canonicalId,\n verifiedEmail: success.verifiedEmail,\n });\n\n return {\n sessionToken: token,\n sessionExpiresAt: expiresAt,\n pafiJwt: success.pafiJwt,\n pafiSessionToken: success.pafiSessionToken,\n canonicalId: success.canonicalId,\n isFirstLogin: success.isFirstLogin,\n ...(success.verifiedEmail ? { verifiedEmail: success.verifiedEmail } : {}),\n };\n }\n}\n","/**\n * Issuer-side abstraction for \"make sure the local user row exists,\n * keyed by the canonical_pafi_user_id the gateway just resolved.\"\n *\n * The SDK can't own this because each issuer has a different\n * `users` table schema (gg56 has `wallet_address`, lotteria doesn't,\n * future issuers might add `phone_number` / `kyc_status` / etc.).\n * Each consuming issuer backend implements `IUserStore` once and\n * passes the class to `PafiDirectAuthModule.forRoot({ userStore })`.\n *\n * Idempotency contract: callers invoke this on every successful\n * direct-auth flow (email OTP / Google / Kakao). Implementation MUST\n * be idempotent — re-calling with the same args is a no-op, NOT an\n * error.\n *\n * Lookup precedence (recommended): canonical_id first, fall back to\n * email. Same canonical across issuers = cross-issuer wallet merge.\n *\n * Race-safety: parallel requests for the same brand-new canonical\n * should be tolerated. The SDK assumes the underlying DB enforces a\n * unique constraint on canonical_id; on conflict, treat as \"row\n * already exists\" rather than re-throwing.\n *\n * @example\n * ```ts\n * @Injectable()\n * class Gg56UserStore implements IUserStore {\n * constructor(\n * @InjectRepository(UserEntity)\n * private readonly users: Repository<UserEntity>,\n * ) {}\n *\n * async upsertByCanonicalAndEmail(args: {\n * canonicalId: string;\n * verifiedEmail?: string;\n * }): Promise<void> {\n * const byCanonical = await this.users.findOne({\n * where: { canonicalId: args.canonicalId },\n * });\n * if (byCanonical) return;\n * // ... fall back to email, create row, race-handle, etc.\n * }\n * }\n * ```\n */\nexport interface IUserStore {\n upsertByCanonicalAndEmail(args: {\n /** canonical_pafi_user_id the gateway just minted. Always present. */\n canonicalId: string;\n /**\n * Verified email when the auth method exposed one (email OTP +\n * Google always; Kakao only if user shared email at consent).\n * Issuer may use to display profile, NOT for primary user lookup\n * (canonicalId is the stable id).\n */\n verifiedEmail?: string;\n }): Promise<void>;\n}\n\n/** DI token for IUserStore. Use with `@Inject(USER_STORE)`. */\nexport const USER_STORE = Symbol(\"USER_STORE\");\n","/**\n * Issuer-side abstraction for \"mint an issuer-native session token\n * the FE will Bearer-auth with for all subsequent /api calls.\"\n *\n * The SDK can't own this because:\n * - Each issuer signs with their own `JWT_SECRET` (HS256) OR with\n * their own RSA/ECDSA key — SDK shouldn't see secrets\n * - Token shape may differ (sub format, scope claims, extra claims\n * like role/tier specific to the issuer's app)\n * - Lifetime may differ (24h default, but some issuers want shorter)\n * - Existing JwtGuard / SessionTokenGuard in the issuer backend\n * expects a specific shape — SDK can't predict it\n *\n * The SDK orchestrator calls `mint()` once per successful direct-auth\n * flow, BEFORE returning the bundled response to the issuer's HTTP\n * controller. The minter is responsible for:\n * 1. Producing a token the issuer's existing guards accept\n * 2. Embedding `authAttribute: {type, value}` if the issuer's\n * /wallet/exchange-pafi-jwt refresh path needs it (most do — see\n * ISSUER_INTEGRATION_GUIDE.md §9 refresh path)\n * 3. Computing absolute expiry timestamp the FE can show\n *\n * @example\n * ```ts\n * @Injectable()\n * class Gg56SessionMinter implements ISessionTokenMinter {\n * private readonly secret: Uint8Array;\n * constructor(config: ConfigService) {\n * this.secret = new TextEncoder().encode(\n * config.getOrThrow('JWT_SECRET'),\n * );\n * }\n *\n * async mint(args: {\n * canonicalId: string;\n * verifiedEmail?: string;\n * }): Promise<{ token: string; expiresAt: string }> {\n * const now = Math.floor(Date.now() / 1000);\n * const exp = now + 86400;\n * const token = await new SignJWT({\n * userKey: args.canonicalId,\n * loginType: 'pafi-direct',\n * scope: 'pafi-session',\n * ...(args.verifiedEmail\n * ? { authAttribute: { type: 'email', value: args.verifiedEmail } }\n * : {}),\n * })\n * .setProtectedHeader({ alg: 'HS256', typ: 'JWT' })\n * .setSubject(args.canonicalId)\n * .setIssuedAt(now)\n * .setExpirationTime(exp)\n * .sign(this.secret);\n * return { token, expiresAt: new Date(exp * 1000).toISOString() };\n * }\n * }\n * ```\n */\nexport interface ISessionTokenMinter {\n mint(args: {\n /** canonical_pafi_user_id, use as `sub` so downstream identifies user uniformly. */\n canonicalId: string;\n /**\n * Verified email when available. Implementations SHOULD embed this\n * as `authAttribute: { type: 'email', value }` in the JWT body so\n * the legacy /wallet/exchange-pafi-jwt refresh path keeps deriving\n * the same canonical_id from the same attribute across refreshes.\n */\n verifiedEmail?: string;\n }): Promise<{\n /** The signed session token (any JWT shape — issuer guards must accept). */\n token: string;\n /** ISO 8601 absolute expiry timestamp. Returned to FE so it can show \"expires at X\". */\n expiresAt: string;\n }>;\n}\n\n/** DI token for ISessionTokenMinter. Use with `@Inject(SESSION_TOKEN_MINTER)`. */\nexport const SESSION_TOKEN_MINTER = Symbol(\"SESSION_TOKEN_MINTER\");\n","import {\n Body,\n Controller,\n HttpCode,\n HttpStatus,\n Post,\n} from \"@nestjs/common\";\nimport { ApiOkResponse, ApiOperation, ApiTags } from \"@nestjs/swagger\";\n\nimport {\n EmailStartRequestDto,\n EmailStartResponseDto,\n EmailVerifyRequestDto,\n GoogleExchangeRequestDto,\n KakaoExchangeRequestDto,\n PafiAuthSuccessDto,\n} from \"./pafi-direct-auth.dto\";\nimport { PafiDirectAuthService } from \"./services/pafi-direct-auth.service\";\n\n/**\n * `/auth/v2/*` — gateway-owned auth endpoints (2026-06-30 direct-auth\n * surface, shipped by `@pafi-dev/issuer/direct-auth`).\n *\n * Issuer backend no longer verifies OTP codes, OAuth id_tokens, or\n * holds OAuth client_secrets. All of that lives at the PAFI gateway.\n * These endpoints are thin proxies that forward user input via\n * {@link PafiDirectAuthService}, then upsert the local user row (via\n * the issuer-provided {@link IUserStore}) + mint an issuer-native\n * session token (via the issuer-provided {@link ISessionTokenMinter})\n * wrapping the gateway-assigned canonical_id.\n *\n * Mounted automatically by `PafiDirectAuthModule.forRoot(options)`.\n * Issuer apps that want to layer extra auth-error handling (e.g. a\n * custom `AuthErrorFilter`) can wrap the controller's routes via a\n * NestJS global filter — no per-route `@UseFilters` needed here.\n */\n@ApiTags(\"pafi-auth-v2\")\n@Controller(\"auth/v2\")\nexport class PafiDirectAuthController {\n constructor(private readonly directAuth: PafiDirectAuthService) {}\n\n @Post(\"email/start\")\n @HttpCode(HttpStatus.OK)\n @ApiOperation({\n summary: \"Step 1: ask gateway to send an OTP to the user email.\",\n description:\n \"Gateway generates the OTP, sends it via its configured email provider, and returns an opaque challenge_id. The FE echoes that challenge_id back on step 2 along with the code the user typed.\",\n })\n @ApiOkResponse({ type: EmailStartResponseDto })\n async startEmail(\n @Body() body: EmailStartRequestDto,\n ): Promise<EmailStartResponseDto> {\n const res = await this.directAuth.startEmail({ email: body.email });\n return {\n challengeId: res.challengeId,\n expiresInSec: res.expiresInSec,\n };\n }\n\n @Post(\"email/verify\")\n @HttpCode(HttpStatus.OK)\n @ApiOperation({\n summary: \"Step 2: submit the OTP to complete email sign-in.\",\n description:\n \"Gateway verifies the OTP, derives canonical_id from the verified email, and mints both a pafi_session_token (24h, gateway-signed) and pafi_jwt (60s, for Privy.loginWithCustomAuth). Issuer wraps these in a session token of its own (sub = canonical_id) so existing guards keep working.\",\n })\n @ApiOkResponse({ type: PafiAuthSuccessDto })\n async verifyEmail(\n @Body() body: EmailVerifyRequestDto,\n ): Promise<PafiAuthSuccessDto> {\n return this.directAuth.verifyEmail({\n challengeId: body.challengeId,\n otpCode: body.otpCode,\n });\n }\n\n @Post(\"google/exchange\")\n @HttpCode(HttpStatus.OK)\n @ApiOperation({\n summary: \"Sign in with Google.\",\n description:\n \"Hand the gateway a Google-issued id_token (FE obtains via Google Identity Services using PAFI's shared client_id). Gateway verifies signature + email_verified, derives canonical_id from the email, returns the same token bundle as /email/verify.\",\n })\n @ApiOkResponse({ type: PafiAuthSuccessDto })\n async exchangeGoogle(\n @Body() body: GoogleExchangeRequestDto,\n ): Promise<PafiAuthSuccessDto> {\n return this.directAuth.exchangeGoogle({ idToken: body.idToken });\n }\n\n @Post(\"kakao/exchange\")\n @HttpCode(HttpStatus.OK)\n @ApiOperation({\n summary: \"Sign in with Kakao.\",\n description:\n \"Hand the gateway the authorization code Kakao redirected back to the FE. Gateway exchanges with Kakao server-to-server (using PAFI-held client_secret), verifies the id_token, and returns the same token bundle as /email/verify. canonical_id derives from email when present, else from the Kakao sub.\",\n })\n @ApiOkResponse({ type: PafiAuthSuccessDto })\n async exchangeKakao(\n @Body() body: KakaoExchangeRequestDto,\n ): Promise<PafiAuthSuccessDto> {\n return this.directAuth.exchangeKakao({\n code: body.code,\n redirectUri: body.redirectUri,\n });\n }\n}\n","import { ApiProperty } from \"@nestjs/swagger\";\nimport {\n IsEmail,\n IsNotEmpty,\n IsOptional,\n IsString,\n IsUrl,\n Length,\n MaxLength,\n} from \"class-validator\";\n\n// ── Requests ────────────────────────────────────────────────────────\n\nexport class EmailStartRequestDto {\n @ApiProperty({ example: \"user1@example.com\" })\n @IsEmail()\n @MaxLength(320)\n email!: string;\n}\n\nexport class EmailVerifyRequestDto {\n @ApiProperty({\n description:\n \"Challenge id returned by POST /auth/v2/email/start. Opaque to the FE; echo verbatim.\",\n })\n @IsString()\n @IsNotEmpty()\n @MaxLength(128)\n challengeId!: string;\n\n @ApiProperty({ example: \"123456\" })\n @IsString()\n @Length(4, 10)\n otpCode!: string;\n}\n\nexport class GoogleExchangeRequestDto {\n @ApiProperty({\n description:\n \"Google-issued ID token (JWS). Obtain on FE via Google Identity Services using PAFI's Google OAuth client_id.\",\n })\n @IsString()\n @IsNotEmpty()\n @MaxLength(8192)\n idToken!: string;\n}\n\nexport class KakaoExchangeRequestDto {\n @ApiProperty({\n description:\n \"Authorization code returned by Kakao to the FE redirect URL.\",\n })\n @IsString()\n @IsNotEmpty()\n @MaxLength(2048)\n code!: string;\n\n @ApiProperty({\n description:\n \"Redirect URI the FE used when initiating the Kakao flow. Optional — gateway falls back to its own KAKAO_REDIRECT_URI env.\",\n required: false,\n })\n @IsOptional()\n @IsUrl({ require_tld: false, require_protocol: true })\n @MaxLength(2048)\n redirectUri?: string;\n}\n\n// ── Responses ───────────────────────────────────────────────────────\n\nexport class EmailStartResponseDto {\n @ApiProperty()\n challengeId!: string;\n\n @ApiProperty({ description: \"Seconds until the challenge expires.\" })\n expiresInSec!: number;\n}\n\nexport class PafiAuthSuccessDto {\n @ApiProperty({\n description:\n \"Issuer-native session token (typically HS256, minted by ISessionTokenMinter) — Bearer-auth for subsequent issuer API calls.\",\n })\n sessionToken!: string;\n\n @ApiProperty({ description: \"Issuer session token expiration (ISO 8601).\" })\n sessionExpiresAt!: string;\n\n @ApiProperty({\n description:\n \"Short-lived PAFI JWT (60s) — FE feeds verbatim to Privy.loginWithCustomAuth() to provision the embedded wallet.\",\n })\n pafiJwt!: string;\n\n @ApiProperty({\n description:\n \"Long-lived PAFI session token (24h) — opaque to FE; keep alongside sessionToken if you ever need to call the gateway directly.\",\n })\n pafiSessionToken!: string;\n\n @ApiProperty({ description: \"canonical_pafi_user_id assigned by the gateway.\" })\n canonicalId!: string;\n\n @ApiProperty({\n description: \"True the first time the user appears at the gateway.\",\n })\n isFirstLogin!: boolean;\n\n @ApiProperty({\n description:\n \"Verified email (when the auth method exposed one — email OTP and Google always; Kakao only if the user shared their email).\",\n required: false,\n })\n verifiedEmail?: string;\n}\n"],"mappings":"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","names":["Module","Injectable","Inject","PAFI_DIRECT_AUTH_MODULE_OPTIONS","Symbol","PafiAuthClientProvider","_client","options","onModuleInit","jwk","clientPrivateJwk","kid","Error","PafiAuthClient","gatewayUrl","issuerId","clientId","client","Injectable","Inject","UnauthorizedException","createRemoteJWKSet","jwtVerify","PafiSessionVerifierService","jwks","expectedIssuer","options","createRemoteJWKSet","URL","gatewayUrl","verify","token","payload","jwtVerify","issuer","err","UnauthorizedException","message","scope","String","sub","exp","iat","verifiedAttribute","parseVerifiedAttribute","verified_attribute","issuerId","issuer_id","undefined","raw","obj","type","valueHash","value_hash","Injectable","Inject","Logger","USER_STORE","Symbol","SESSION_TOKEN_MINTER","Symbol","PafiDirectAuthService","logger","Logger","name","clientProvider","sessionVerifier","userStore","sessionTokenMinter","startEmail","args","client","email","correlationId","verifyEmail","success","challengeId","otpCode","verify","pafiSessionToken","finalize","exchangeGoogle","idToken","exchangeKakao","code","redirectUri","upsertByCanonicalAndEmail","canonicalId","verifiedEmail","token","expiresAt","mint","sessionToken","sessionExpiresAt","pafiJwt","isFirstLogin","Body","Controller","HttpCode","HttpStatus","Post","ApiOkResponse","ApiOperation","ApiTags","ApiProperty","IsEmail","IsNotEmpty","IsOptional","IsString","IsUrl","Length","MaxLength","EmailStartRequestDto","email","example","EmailVerifyRequestDto","challengeId","otpCode","description","GoogleExchangeRequestDto","idToken","KakaoExchangeRequestDto","code","redirectUri","required","require_tld","require_protocol","EmailStartResponseDto","expiresInSec","PafiAuthSuccessDto","sessionToken","sessionExpiresAt","pafiJwt","pafiSessionToken","canonicalId","isFirstLogin","verifiedEmail","PafiDirectAuthController","directAuth","startEmail","body","res","email","challengeId","expiresInSec","verifyEmail","otpCode","exchangeGoogle","idToken","exchangeKakao","code","redirectUri","OK","summary","description","type","EmailStartResponseDto","PafiAuthSuccessDto","PafiDirectAuthModule","forRoot","options","module","imports","controllers","PafiDirectAuthController","providers","provide","PAFI_DIRECT_AUTH_MODULE_OPTIONS","useValue","userStore","sessionTokenMinter","USER_STORE","useExisting","SESSION_TOKEN_MINTER","PafiAuthClientProvider","PafiSessionVerifierService","PafiDirectAuthService","exports"]}
|
package/dist/http/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
7
|
var __export = (target, all) => {
|
|
7
8
|
for (var name in all)
|
|
8
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -46,17 +47,20 @@ function buildSdkErrorBody(err) {
|
|
|
46
47
|
if (err.details !== void 0) body.details = err.details;
|
|
47
48
|
return body;
|
|
48
49
|
}
|
|
50
|
+
__name(buildSdkErrorBody, "buildSdkErrorBody");
|
|
49
51
|
|
|
50
52
|
// src/http/errorEnvelope.ts
|
|
51
53
|
function isValidationPipeBody(body) {
|
|
52
54
|
return Array.isArray(body["message"]) && typeof body["error"] === "string" && body["error"] === "Bad Request";
|
|
53
55
|
}
|
|
56
|
+
__name(isValidationPipeBody, "isValidationPipeBody");
|
|
54
57
|
function extractParamFromValidatorMessage(message) {
|
|
55
58
|
const idx = message.indexOf(" ");
|
|
56
59
|
if (idx <= 0) return void 0;
|
|
57
60
|
const candidate = message.slice(0, idx);
|
|
58
61
|
return /^[A-Za-z_][\w.]*$/.test(candidate) ? candidate : void 0;
|
|
59
62
|
}
|
|
63
|
+
__name(extractParamFromValidatorMessage, "extractParamFromValidatorMessage");
|
|
60
64
|
function normalizeValidationPipeBody(body) {
|
|
61
65
|
const fields = {};
|
|
62
66
|
for (const msg of body.message) {
|
|
@@ -71,11 +75,14 @@ function normalizeValidationPipeBody(body) {
|
|
|
71
75
|
code: "VALIDATION_FAILED",
|
|
72
76
|
message: body.message.join("; "),
|
|
73
77
|
safeToRetry: false,
|
|
74
|
-
metadata: {
|
|
78
|
+
metadata: {
|
|
79
|
+
fieldErrors: fields
|
|
80
|
+
}
|
|
75
81
|
};
|
|
76
82
|
if (param) payload.param = param;
|
|
77
83
|
return payload;
|
|
78
84
|
}
|
|
85
|
+
__name(normalizeValidationPipeBody, "normalizeValidationPipeBody");
|
|
79
86
|
function normalizeHttpExceptionBody(desc) {
|
|
80
87
|
const { statusCode, responseBody, exceptionName, fallbackMessage } = desc;
|
|
81
88
|
const defaultType = (0, import_core.defaultErrorTypeForStatus)(statusCode);
|
|
@@ -116,6 +123,7 @@ function normalizeHttpExceptionBody(desc) {
|
|
|
116
123
|
safeToRetry: false
|
|
117
124
|
};
|
|
118
125
|
}
|
|
126
|
+
__name(normalizeHttpExceptionBody, "normalizeHttpExceptionBody");
|
|
119
127
|
function payloadFromPafiSdkError(err) {
|
|
120
128
|
const body = buildSdkErrorBody(err);
|
|
121
129
|
const payload = {
|
|
@@ -129,10 +137,12 @@ function payloadFromPafiSdkError(err) {
|
|
|
129
137
|
if (body.details !== void 0) payload.details = body.details;
|
|
130
138
|
return payload;
|
|
131
139
|
}
|
|
140
|
+
__name(payloadFromPafiSdkError, "payloadFromPafiSdkError");
|
|
132
141
|
function sanitizeDbErrorMessage(message) {
|
|
133
142
|
if (/^[A-Z_]+: /.test(message) && message.length < 256) return message;
|
|
134
143
|
return "Internal database error";
|
|
135
144
|
}
|
|
145
|
+
__name(sanitizeDbErrorMessage, "sanitizeDbErrorMessage");
|
|
136
146
|
function buildErrorEnvelope(input) {
|
|
137
147
|
const now = (input.ctx.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
138
148
|
return {
|
|
@@ -146,9 +156,11 @@ function buildErrorEnvelope(input) {
|
|
|
146
156
|
}
|
|
147
157
|
};
|
|
148
158
|
}
|
|
159
|
+
__name(buildErrorEnvelope, "buildErrorEnvelope");
|
|
149
160
|
function payloadFromHttpException(desc) {
|
|
150
161
|
return normalizeHttpExceptionBody(desc);
|
|
151
162
|
}
|
|
163
|
+
__name(payloadFromHttpException, "payloadFromHttpException");
|
|
152
164
|
function payloadFromGenericError(err) {
|
|
153
165
|
const name = err.name || "INTERNAL_SERVER_ERROR";
|
|
154
166
|
const isDbError = name === "QueryFailedError" || name === "EntityNotFoundError";
|
|
@@ -159,6 +171,7 @@ function payloadFromGenericError(err) {
|
|
|
159
171
|
safeToRetry: false
|
|
160
172
|
};
|
|
161
173
|
}
|
|
174
|
+
__name(payloadFromGenericError, "payloadFromGenericError");
|
|
162
175
|
// Annotate the CommonJS export names for ESM import in node:
|
|
163
176
|
0 && (module.exports = {
|
|
164
177
|
buildErrorEnvelope,
|
package/dist/http/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/http/index.ts","../../src/errors.ts","../../src/api/errorMapper.ts","../../src/http/errorEnvelope.ts"],"sourcesContent":["export {\n buildErrorEnvelope,\n payloadFromHttpException,\n payloadFromGenericError,\n payloadFromPafiSdkError,\n type PafiErrorEnvelope,\n type PafiErrorPayload,\n type GenericHttpExceptionDescriptor,\n type NormalizeContext,\n} from \"./errorEnvelope\";\n","/**\n * `PafiSdkError` + `SdkErrorHttpStatus` are exported from\n * `@pafi-dev/core/errors` so core-level errors (e.g. `OracleStaleError`)\n * can extend the same base. Issuer re-exports the canonical types here\n * for back-compat — `instanceof PafiSdkError` from EITHER package\n * catches errors thrown from EITHER package.\n */\nexport {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type SdkErrorHttpStatus,\n type PafiErrorType,\n} from \"@pafi-dev/core\";\nimport { PafiSdkError } from \"@pafi-dev/core\";\n\n/**\n * `ValidationError` lives in `@pafi-dev/core` so core/trading helpers\n * throw the same typed class. Re-exported here for back-compat.\n */\nexport { ValidationError } from \"@pafi-dev/core\";\n\n/**\n * Issuer wired the SDK without a dependency the requested endpoint\n * needs (e.g. `/gas-fee` called but `feeManager` not configured;\n * `/pools` called but `poolsProvider` not configured). 503 because\n * the endpoint genuinely can't serve the request — caller's payload\n * is fine, the issuer's deployment is incomplete.\n */\nexport class ConfigurationError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code: string;\n readonly details?: Record<string, unknown>;\n\n constructor(\n code: string,\n message: string,\n details?: Record<string, unknown>,\n ) {\n super(message);\n this.code = code;\n this.details = details;\n }\n}\n","import {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type PafiErrorType,\n type SdkErrorHttpStatus,\n} from \"../errors\";\n\n/**\n * Normalized HTTP status the issuer controller should surface for a\n * given SDK error. Mirrors `SdkErrorHttpStatus` on `PafiSdkError`.\n */\nexport type SdkErrorStatus = SdkErrorHttpStatus;\n\n/**\n * Structured body the issuer controller passes to its\n * framework-specific exception class. Stripe-style envelope:\n *\n * ```json\n * {\n * \"type\": \"business_logic_error\",\n * \"code\": \"REDEMPTION_POLICY_DENIED\",\n * \"message\": \"...\",\n * \"param\": null,\n * \"metadata\": { \"policyDenialCode\": \"PER_TX_MIN\" },\n * \"safeToRetry\": false\n * }\n * ```\n *\n * Carries enough fields for the global HTTP filter to emit the final\n * envelope without losing any SDK-side context.\n */\nexport interface SdkErrorBody {\n /** Stripe-style taxonomy slot — drives UI branching. */\n type: PafiErrorType;\n /** Machine-readable code, e.g. `\"REDEMPTION_POLICY_DENIED\"`. */\n code: string;\n /** Human-readable message. */\n message: string;\n /** Field name that triggered the error, when applicable. */\n param?: string;\n /** UI-facing structured context. */\n metadata?: Record<string, unknown>;\n /** Raw debug context. */\n details?: unknown;\n /** True when retry is safe (no side effects yet). */\n safeToRetry: boolean;\n}\n\n/**\n * Per-status exception factories. The issuer's controller wires one\n * factory per status to its preferred framework's exception class\n * (NestJS `UnprocessableEntityException`, Fastify `httpErrors.badData`,\n * etc). The factory must return an Error — `createSdkErrorMapper`\n * uses `throw factory(body)`.\n */\nexport interface SdkErrorMapperFactories {\n notFound: (body: SdkErrorBody) => Error;\n forbidden: (body: SdkErrorBody) => Error;\n unprocessable: (body: SdkErrorBody) => Error;\n serviceUnavailable: (body: SdkErrorBody) => Error;\n}\n\n/**\n * Build the Stripe-style body from any `PafiSdkError`. Exposed for\n * frameworks that don't fit the four-factory shape (e.g. a Hono error\n * handler that builds its own response object).\n */\nexport function buildSdkErrorBody(err: PafiSdkError): SdkErrorBody {\n const type =\n err.type ??\n defaultErrorTypeForStatus(SDK_ERROR_HTTP_STATUS_CODE[err.httpStatus]);\n const body: SdkErrorBody = {\n type,\n code: err.code,\n message: err.message,\n safeToRetry: err.safeToRetry,\n };\n if (err.param) body.param = err.param;\n if (err.metadata) body.metadata = err.metadata;\n if (err.details !== undefined) body.details = err.details;\n return body;\n}\n\n/**\n * Build a single error-mapping function that converts any `PafiSdkError`\n * into the issuer's framework-specific HTTP exception. Status, code,\n * `safeToRetry`, and `details` come straight off the error class —\n * the mapper is a dumb funnel, no per-error business logic.\n *\n * Any non-`PafiSdkError` is re-thrown unchanged so unexpected runtime\n * errors propagate to the framework's default 500 handler.\n *\n * Usage (NestJS):\n *\n * ```ts\n * const mapSdkError = createSdkErrorMapper({\n * notFound: (body) => new NotFoundException(body),\n * forbidden: (body) => new ForbiddenException(body),\n * unprocessable: (body) => new UnprocessableEntityException(body),\n * serviceUnavailable: (body) => new ServiceUnavailableException(body),\n * });\n *\n * try { ... } catch (err) { mapSdkError(err); }\n * ```\n *\n * Returns `never` so call sites in `try/catch` propagate the throw\n * without a redundant `throw` keyword.\n */\nexport function createSdkErrorMapper(\n factories: SdkErrorMapperFactories,\n): (err: unknown) => never {\n return (err: unknown): never => {\n if (!(err instanceof PafiSdkError)) {\n throw err;\n }\n const body = buildSdkErrorBody(err);\n switch (err.httpStatus) {\n case \"not_found\":\n throw factories.notFound(body);\n case \"forbidden\":\n throw factories.forbidden(body);\n case \"unprocessable\":\n throw factories.unprocessable(body);\n case \"service_unavailable\":\n throw factories.serviceUnavailable(body);\n }\n };\n}\n","/**\n * Stripe-style HTTP error envelope shared by every PAFI service and\n * issuer backend. The framework-agnostic `normalizeErrorToEnvelope`\n * helper produces this from any thrown value; framework-specific\n * filters (`@pafi-dev/issuer/nestjs`) call into it and write the\n * result to their response object.\n *\n * Wire format:\n *\n * ```json\n * {\n * \"success\": false,\n * \"statusCode\": 422,\n * \"error\": {\n * \"type\": \"business_logic_error\",\n * \"code\": \"REDEMPTION_POLICY_DENIED\",\n * \"message\": \"redemption denied: amount 1 below per-tx minimum\",\n * \"param\": null,\n * \"metadata\": { \"policyDenialCode\": \"PER_TX_MIN\" },\n * \"safeToRetry\": false,\n * \"details\": null\n * },\n * \"meta\": {\n * \"timestamp\": \"2026-05-07T...\",\n * \"requestId\": \"...\",\n * \"path\": \"/pt/redeem\"\n * }\n * }\n * ```\n */\n\nimport {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type PafiErrorType,\n} from \"../errors\";\nimport { buildSdkErrorBody, type SdkErrorBody } from \"../api/errorMapper\";\n\n/** Inner `error` block of the envelope. */\nexport interface PafiErrorPayload {\n type: PafiErrorType;\n code: string;\n message: string;\n param?: string;\n metadata?: Record<string, unknown>;\n details?: unknown;\n safeToRetry: boolean;\n}\n\n/** Outer envelope returned for any non-2xx response. */\nexport interface PafiErrorEnvelope {\n success: false;\n statusCode: number;\n error: PafiErrorPayload;\n meta: {\n timestamp: string;\n requestId: string;\n path: string;\n };\n}\n\n/** Per-call request context the filter must collect from the host framework. */\nexport interface NormalizeContext {\n /** `req.url` or equivalent. */\n path: string;\n /** Resolved request id (header or generated). */\n requestId: string;\n /** Optional `now()` injection for deterministic tests. */\n now?: () => Date;\n}\n\n/**\n * Generic error duck-type. The filter passes a small descriptor for\n * any framework exception so the normalizer doesn't depend on\n * `@nestjs/common` or any specific HTTP library.\n */\nexport interface GenericHttpExceptionDescriptor {\n /** HTTP status code carried by the exception. */\n statusCode: number;\n /**\n * The body the framework would have serialized. For NestJS this is\n * `exception.getResponse()`. May be a string or a record with\n * `code`/`error`/`message`/`details`/`safeToRetry`/`type`/etc.\n */\n responseBody: unknown;\n /** Exception class name, used as a last-resort `code` fallback. */\n exceptionName: string;\n /** Original `error.message`, used as a last-resort `message` fallback. */\n fallbackMessage: string;\n}\n\n/** True when `value` looks like a NestJS `BadRequestException` from `ValidationPipe`. */\nfunction isValidationPipeBody(\n body: Record<string, unknown>,\n): body is { message: string[]; error?: string; statusCode?: number } {\n return (\n Array.isArray(body[\"message\"]) &&\n typeof body[\"error\"] === \"string\" &&\n body[\"error\"] === \"Bad Request\"\n );\n}\n\n/** Pull `param` out of a `class-validator` message like `\"amount must be a number string\"`. */\nfunction extractParamFromValidatorMessage(message: string): string | undefined {\n const idx = message.indexOf(\" \");\n if (idx <= 0) return undefined;\n const candidate = message.slice(0, idx);\n return /^[A-Za-z_][\\w.]*$/.test(candidate) ? candidate : undefined;\n}\n\nfunction normalizeValidationPipeBody(\n body: { message: string[]; statusCode?: number },\n): PafiErrorPayload {\n const fields: Record<string, string[]> = {};\n for (const msg of body.message) {\n const param = extractParamFromValidatorMessage(msg);\n const key = param ?? \"_\";\n (fields[key] ??= []).push(msg);\n }\n const fieldKeys = Object.keys(fields).filter((k) => k !== \"_\");\n const param = fieldKeys.length === 1 ? fieldKeys[0] : undefined;\n\n const payload: PafiErrorPayload = {\n type: \"validation_error\",\n code: \"VALIDATION_FAILED\",\n message: body.message.join(\"; \"),\n safeToRetry: false,\n metadata: { fieldErrors: fields },\n };\n if (param) payload.param = param;\n return payload;\n}\n\n/**\n * Pull a Stripe-style payload out of a structured exception body. The\n * filter has already determined this is an `HttpException` (or\n * duck-typed equivalent). Order:\n *\n * 1. SDK-shaped body (has `type` + `code`) — pass through verbatim.\n * 2. `ValidationPipe` body (`message: string[]`, `error: \"Bad Request\"`)\n * — collapse into `validation_error` with `metadata.fieldErrors`.\n * 3. Plain object with `code` or `error` — best-effort extract.\n * 4. String body — just a message.\n */\nfunction normalizeHttpExceptionBody(\n desc: GenericHttpExceptionDescriptor,\n): PafiErrorPayload {\n const { statusCode, responseBody, exceptionName, fallbackMessage } = desc;\n const defaultType = defaultErrorTypeForStatus(statusCode);\n\n if (typeof responseBody === \"string\") {\n return {\n type: defaultType,\n code: exceptionName,\n message: responseBody,\n safeToRetry: false,\n };\n }\n\n if (responseBody && typeof responseBody === \"object\") {\n const body = responseBody as Record<string, unknown>;\n\n if (isValidationPipeBody(body)) {\n return normalizeValidationPipeBody(body);\n }\n\n const code =\n (typeof body[\"code\"] === \"string\" && body[\"code\"]) ||\n (typeof body[\"error\"] === \"string\" && body[\"error\"]) ||\n exceptionName;\n const message =\n (typeof body[\"message\"] === \"string\" && body[\"message\"]) ||\n (Array.isArray(body[\"message\"])\n ? (body[\"message\"] as string[]).join(\"; \")\n : \"\") ||\n fallbackMessage;\n const type =\n (typeof body[\"type\"] === \"string\" && (body[\"type\"] as PafiErrorType)) ||\n defaultType;\n const safeToRetry =\n typeof body[\"safeToRetry\"] === \"boolean\" ? body[\"safeToRetry\"] : false;\n\n const payload: PafiErrorPayload = {\n type,\n code,\n message,\n safeToRetry,\n };\n if (typeof body[\"param\"] === \"string\") payload.param = body[\"param\"];\n if (body[\"metadata\"] && typeof body[\"metadata\"] === \"object\") {\n payload.metadata = body[\"metadata\"] as Record<string, unknown>;\n }\n if (body[\"details\"] !== undefined) payload.details = body[\"details\"];\n return payload;\n }\n\n return {\n type: defaultType,\n code: exceptionName || \"INTERNAL_SERVER_ERROR\",\n message: fallbackMessage || \"An unexpected error occurred\",\n safeToRetry: false,\n };\n}\n\n/**\n * Convert a `PafiSdkError` directly to the inner `error` payload —\n * skipping any framework exception wrapper. Used by frameworks that\n * surface SDK errors without going through `createSdkErrorMapper`.\n */\nexport function payloadFromPafiSdkError(err: PafiSdkError): PafiErrorPayload {\n const body: SdkErrorBody = buildSdkErrorBody(err);\n const payload: PafiErrorPayload = {\n type: body.type,\n code: body.code,\n message: body.message,\n safeToRetry: body.safeToRetry,\n };\n if (body.param) payload.param = body.param;\n if (body.metadata) payload.metadata = body.metadata;\n if (body.details !== undefined) payload.details = body.details;\n return payload;\n}\n\n/** Strip SQL fragments from raw DB driver errors before exposing them. */\nfunction sanitizeDbErrorMessage(message: string): string {\n if (/^[A-Z_]+: /.test(message) && message.length < 256) return message;\n return \"Internal database error\";\n}\n\n/**\n * Build the full envelope for any thrown value. The caller resolves\n * `HttpException`-shaped exceptions and passes a descriptor; plain\n * `Error` instances and unknown throws are handled directly.\n */\nexport function buildErrorEnvelope(input: {\n status: number;\n payload: PafiErrorPayload;\n ctx: NormalizeContext;\n}): PafiErrorEnvelope {\n const now = (input.ctx.now ?? (() => new Date()))();\n return {\n success: false,\n statusCode: input.status,\n error: input.payload,\n meta: {\n timestamp: now.toISOString(),\n requestId: input.ctx.requestId,\n path: input.ctx.path,\n },\n };\n}\n\n/**\n * Normalize a known `HttpException`-shaped exception into a payload.\n * Framework filters supply the descriptor; the rest is shape-agnostic.\n */\nexport function payloadFromHttpException(\n desc: GenericHttpExceptionDescriptor,\n): PafiErrorPayload {\n return normalizeHttpExceptionBody(desc);\n}\n\n/**\n * Normalize a generic `Error` (not an HttpException) — TypeORM\n * `QueryFailedError`, viem revert errors, etc. Returns a redacted\n * 500-class payload with no message bleed-through unless the error\n * is recognizably benign.\n */\nexport function payloadFromGenericError(err: Error): PafiErrorPayload {\n const name = err.name || \"INTERNAL_SERVER_ERROR\";\n const isDbError = name === \"QueryFailedError\" || name === \"EntityNotFoundError\";\n return {\n type: \"server_error\",\n code: name,\n message: isDbError\n ? sanitizeDbErrorMessage(err.message)\n : err.message || \"An unexpected error occurred\",\n safeToRetry: false,\n };\n}\n"],"mappings":"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","names":["import_core","param"]}
|
|
1
|
+
{"version":3,"sources":["../../src/http/index.ts","../../src/errors.ts","../../src/api/errorMapper.ts","../../src/http/errorEnvelope.ts"],"sourcesContent":["export {\n buildErrorEnvelope,\n payloadFromHttpException,\n payloadFromGenericError,\n payloadFromPafiSdkError,\n type PafiErrorEnvelope,\n type PafiErrorPayload,\n type GenericHttpExceptionDescriptor,\n type NormalizeContext,\n} from \"./errorEnvelope\";\n","/**\n * `PafiSdkError` + `SdkErrorHttpStatus` are exported from\n * `@pafi-dev/core/errors` so core-level errors (e.g. `OracleStaleError`)\n * can extend the same base. Issuer re-exports the canonical types here\n * for back-compat — `instanceof PafiSdkError` from EITHER package\n * catches errors thrown from EITHER package.\n */\nexport {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type SdkErrorHttpStatus,\n type PafiErrorType,\n} from \"@pafi-dev/core\";\nimport { PafiSdkError } from \"@pafi-dev/core\";\n\n/**\n * `ValidationError` lives in `@pafi-dev/core` so core/trading helpers\n * throw the same typed class. Re-exported here for back-compat.\n */\nexport { ValidationError } from \"@pafi-dev/core\";\n\n/**\n * Issuer wired the SDK without a dependency the requested endpoint\n * needs (e.g. `/gas-fee` called but `feeManager` not configured;\n * `/pools` called but `poolsProvider` not configured). 503 because\n * the endpoint genuinely can't serve the request — caller's payload\n * is fine, the issuer's deployment is incomplete.\n */\nexport class ConfigurationError extends PafiSdkError {\n readonly httpStatus = \"service_unavailable\" as const;\n readonly code: string;\n readonly details?: Record<string, unknown>;\n\n constructor(\n code: string,\n message: string,\n details?: Record<string, unknown>,\n ) {\n super(message);\n this.code = code;\n this.details = details;\n }\n}\n","import {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type PafiErrorType,\n type SdkErrorHttpStatus,\n} from \"../errors\";\n\n/**\n * Normalized HTTP status the issuer controller should surface for a\n * given SDK error. Mirrors `SdkErrorHttpStatus` on `PafiSdkError`.\n */\nexport type SdkErrorStatus = SdkErrorHttpStatus;\n\n/**\n * Structured body the issuer controller passes to its\n * framework-specific exception class. Stripe-style envelope:\n *\n * ```json\n * {\n * \"type\": \"business_logic_error\",\n * \"code\": \"REDEMPTION_POLICY_DENIED\",\n * \"message\": \"...\",\n * \"param\": null,\n * \"metadata\": { \"policyDenialCode\": \"PER_TX_MIN\" },\n * \"safeToRetry\": false\n * }\n * ```\n *\n * Carries enough fields for the global HTTP filter to emit the final\n * envelope without losing any SDK-side context.\n */\nexport interface SdkErrorBody {\n /** Stripe-style taxonomy slot — drives UI branching. */\n type: PafiErrorType;\n /** Machine-readable code, e.g. `\"REDEMPTION_POLICY_DENIED\"`. */\n code: string;\n /** Human-readable message. */\n message: string;\n /** Field name that triggered the error, when applicable. */\n param?: string;\n /** UI-facing structured context. */\n metadata?: Record<string, unknown>;\n /** Raw debug context. */\n details?: unknown;\n /** True when retry is safe (no side effects yet). */\n safeToRetry: boolean;\n}\n\n/**\n * Per-status exception factories. The issuer's controller wires one\n * factory per status to its preferred framework's exception class\n * (NestJS `UnprocessableEntityException`, Fastify `httpErrors.badData`,\n * etc). The factory must return an Error — `createSdkErrorMapper`\n * uses `throw factory(body)`.\n */\nexport interface SdkErrorMapperFactories {\n notFound: (body: SdkErrorBody) => Error;\n forbidden: (body: SdkErrorBody) => Error;\n unprocessable: (body: SdkErrorBody) => Error;\n serviceUnavailable: (body: SdkErrorBody) => Error;\n}\n\n/**\n * Build the Stripe-style body from any `PafiSdkError`. Exposed for\n * frameworks that don't fit the four-factory shape (e.g. a Hono error\n * handler that builds its own response object).\n */\nexport function buildSdkErrorBody(err: PafiSdkError): SdkErrorBody {\n const type =\n err.type ??\n defaultErrorTypeForStatus(SDK_ERROR_HTTP_STATUS_CODE[err.httpStatus]);\n const body: SdkErrorBody = {\n type,\n code: err.code,\n message: err.message,\n safeToRetry: err.safeToRetry,\n };\n if (err.param) body.param = err.param;\n if (err.metadata) body.metadata = err.metadata;\n if (err.details !== undefined) body.details = err.details;\n return body;\n}\n\n/**\n * Build a single error-mapping function that converts any `PafiSdkError`\n * into the issuer's framework-specific HTTP exception. Status, code,\n * `safeToRetry`, and `details` come straight off the error class —\n * the mapper is a dumb funnel, no per-error business logic.\n *\n * Any non-`PafiSdkError` is re-thrown unchanged so unexpected runtime\n * errors propagate to the framework's default 500 handler.\n *\n * Usage (NestJS):\n *\n * ```ts\n * const mapSdkError = createSdkErrorMapper({\n * notFound: (body) => new NotFoundException(body),\n * forbidden: (body) => new ForbiddenException(body),\n * unprocessable: (body) => new UnprocessableEntityException(body),\n * serviceUnavailable: (body) => new ServiceUnavailableException(body),\n * });\n *\n * try { ... } catch (err) { mapSdkError(err); }\n * ```\n *\n * Returns `never` so call sites in `try/catch` propagate the throw\n * without a redundant `throw` keyword.\n */\nexport function createSdkErrorMapper(\n factories: SdkErrorMapperFactories,\n): (err: unknown) => never {\n return (err: unknown): never => {\n if (!(err instanceof PafiSdkError)) {\n throw err;\n }\n const body = buildSdkErrorBody(err);\n switch (err.httpStatus) {\n case \"not_found\":\n throw factories.notFound(body);\n case \"forbidden\":\n throw factories.forbidden(body);\n case \"unprocessable\":\n throw factories.unprocessable(body);\n case \"service_unavailable\":\n throw factories.serviceUnavailable(body);\n }\n };\n}\n","/**\n * Stripe-style HTTP error envelope shared by every PAFI service and\n * issuer backend. The framework-agnostic `normalizeErrorToEnvelope`\n * helper produces this from any thrown value; framework-specific\n * filters (`@pafi-dev/issuer/nestjs`) call into it and write the\n * result to their response object.\n *\n * Wire format:\n *\n * ```json\n * {\n * \"success\": false,\n * \"statusCode\": 422,\n * \"error\": {\n * \"type\": \"business_logic_error\",\n * \"code\": \"REDEMPTION_POLICY_DENIED\",\n * \"message\": \"redemption denied: amount 1 below per-tx minimum\",\n * \"param\": null,\n * \"metadata\": { \"policyDenialCode\": \"PER_TX_MIN\" },\n * \"safeToRetry\": false,\n * \"details\": null\n * },\n * \"meta\": {\n * \"timestamp\": \"2026-05-07T...\",\n * \"requestId\": \"...\",\n * \"path\": \"/pt/redeem\"\n * }\n * }\n * ```\n */\n\nimport {\n PafiSdkError,\n SDK_ERROR_HTTP_STATUS_CODE,\n defaultErrorTypeForStatus,\n type PafiErrorType,\n} from \"../errors\";\nimport { buildSdkErrorBody, type SdkErrorBody } from \"../api/errorMapper\";\n\n/** Inner `error` block of the envelope. */\nexport interface PafiErrorPayload {\n type: PafiErrorType;\n code: string;\n message: string;\n param?: string;\n metadata?: Record<string, unknown>;\n details?: unknown;\n safeToRetry: boolean;\n}\n\n/** Outer envelope returned for any non-2xx response. */\nexport interface PafiErrorEnvelope {\n success: false;\n statusCode: number;\n error: PafiErrorPayload;\n meta: {\n timestamp: string;\n requestId: string;\n path: string;\n };\n}\n\n/** Per-call request context the filter must collect from the host framework. */\nexport interface NormalizeContext {\n /** `req.url` or equivalent. */\n path: string;\n /** Resolved request id (header or generated). */\n requestId: string;\n /** Optional `now()` injection for deterministic tests. */\n now?: () => Date;\n}\n\n/**\n * Generic error duck-type. The filter passes a small descriptor for\n * any framework exception so the normalizer doesn't depend on\n * `@nestjs/common` or any specific HTTP library.\n */\nexport interface GenericHttpExceptionDescriptor {\n /** HTTP status code carried by the exception. */\n statusCode: number;\n /**\n * The body the framework would have serialized. For NestJS this is\n * `exception.getResponse()`. May be a string or a record with\n * `code`/`error`/`message`/`details`/`safeToRetry`/`type`/etc.\n */\n responseBody: unknown;\n /** Exception class name, used as a last-resort `code` fallback. */\n exceptionName: string;\n /** Original `error.message`, used as a last-resort `message` fallback. */\n fallbackMessage: string;\n}\n\n/** True when `value` looks like a NestJS `BadRequestException` from `ValidationPipe`. */\nfunction isValidationPipeBody(\n body: Record<string, unknown>,\n): body is { message: string[]; error?: string; statusCode?: number } {\n return (\n Array.isArray(body[\"message\"]) &&\n typeof body[\"error\"] === \"string\" &&\n body[\"error\"] === \"Bad Request\"\n );\n}\n\n/** Pull `param` out of a `class-validator` message like `\"amount must be a number string\"`. */\nfunction extractParamFromValidatorMessage(message: string): string | undefined {\n const idx = message.indexOf(\" \");\n if (idx <= 0) return undefined;\n const candidate = message.slice(0, idx);\n return /^[A-Za-z_][\\w.]*$/.test(candidate) ? candidate : undefined;\n}\n\nfunction normalizeValidationPipeBody(\n body: { message: string[]; statusCode?: number },\n): PafiErrorPayload {\n const fields: Record<string, string[]> = {};\n for (const msg of body.message) {\n const param = extractParamFromValidatorMessage(msg);\n const key = param ?? \"_\";\n (fields[key] ??= []).push(msg);\n }\n const fieldKeys = Object.keys(fields).filter((k) => k !== \"_\");\n const param = fieldKeys.length === 1 ? fieldKeys[0] : undefined;\n\n const payload: PafiErrorPayload = {\n type: \"validation_error\",\n code: \"VALIDATION_FAILED\",\n message: body.message.join(\"; \"),\n safeToRetry: false,\n metadata: { fieldErrors: fields },\n };\n if (param) payload.param = param;\n return payload;\n}\n\n/**\n * Pull a Stripe-style payload out of a structured exception body. The\n * filter has already determined this is an `HttpException` (or\n * duck-typed equivalent). Order:\n *\n * 1. SDK-shaped body (has `type` + `code`) — pass through verbatim.\n * 2. `ValidationPipe` body (`message: string[]`, `error: \"Bad Request\"`)\n * — collapse into `validation_error` with `metadata.fieldErrors`.\n * 3. Plain object with `code` or `error` — best-effort extract.\n * 4. String body — just a message.\n */\nfunction normalizeHttpExceptionBody(\n desc: GenericHttpExceptionDescriptor,\n): PafiErrorPayload {\n const { statusCode, responseBody, exceptionName, fallbackMessage } = desc;\n const defaultType = defaultErrorTypeForStatus(statusCode);\n\n if (typeof responseBody === \"string\") {\n return {\n type: defaultType,\n code: exceptionName,\n message: responseBody,\n safeToRetry: false,\n };\n }\n\n if (responseBody && typeof responseBody === \"object\") {\n const body = responseBody as Record<string, unknown>;\n\n if (isValidationPipeBody(body)) {\n return normalizeValidationPipeBody(body);\n }\n\n const code =\n (typeof body[\"code\"] === \"string\" && body[\"code\"]) ||\n (typeof body[\"error\"] === \"string\" && body[\"error\"]) ||\n exceptionName;\n const message =\n (typeof body[\"message\"] === \"string\" && body[\"message\"]) ||\n (Array.isArray(body[\"message\"])\n ? (body[\"message\"] as string[]).join(\"; \")\n : \"\") ||\n fallbackMessage;\n const type =\n (typeof body[\"type\"] === \"string\" && (body[\"type\"] as PafiErrorType)) ||\n defaultType;\n const safeToRetry =\n typeof body[\"safeToRetry\"] === \"boolean\" ? body[\"safeToRetry\"] : false;\n\n const payload: PafiErrorPayload = {\n type,\n code,\n message,\n safeToRetry,\n };\n if (typeof body[\"param\"] === \"string\") payload.param = body[\"param\"];\n if (body[\"metadata\"] && typeof body[\"metadata\"] === \"object\") {\n payload.metadata = body[\"metadata\"] as Record<string, unknown>;\n }\n if (body[\"details\"] !== undefined) payload.details = body[\"details\"];\n return payload;\n }\n\n return {\n type: defaultType,\n code: exceptionName || \"INTERNAL_SERVER_ERROR\",\n message: fallbackMessage || \"An unexpected error occurred\",\n safeToRetry: false,\n };\n}\n\n/**\n * Convert a `PafiSdkError` directly to the inner `error` payload —\n * skipping any framework exception wrapper. Used by frameworks that\n * surface SDK errors without going through `createSdkErrorMapper`.\n */\nexport function payloadFromPafiSdkError(err: PafiSdkError): PafiErrorPayload {\n const body: SdkErrorBody = buildSdkErrorBody(err);\n const payload: PafiErrorPayload = {\n type: body.type,\n code: body.code,\n message: body.message,\n safeToRetry: body.safeToRetry,\n };\n if (body.param) payload.param = body.param;\n if (body.metadata) payload.metadata = body.metadata;\n if (body.details !== undefined) payload.details = body.details;\n return payload;\n}\n\n/** Strip SQL fragments from raw DB driver errors before exposing them. */\nfunction sanitizeDbErrorMessage(message: string): string {\n if (/^[A-Z_]+: /.test(message) && message.length < 256) return message;\n return \"Internal database error\";\n}\n\n/**\n * Build the full envelope for any thrown value. The caller resolves\n * `HttpException`-shaped exceptions and passes a descriptor; plain\n * `Error` instances and unknown throws are handled directly.\n */\nexport function buildErrorEnvelope(input: {\n status: number;\n payload: PafiErrorPayload;\n ctx: NormalizeContext;\n}): PafiErrorEnvelope {\n const now = (input.ctx.now ?? (() => new Date()))();\n return {\n success: false,\n statusCode: input.status,\n error: input.payload,\n meta: {\n timestamp: now.toISOString(),\n requestId: input.ctx.requestId,\n path: input.ctx.path,\n },\n };\n}\n\n/**\n * Normalize a known `HttpException`-shaped exception into a payload.\n * Framework filters supply the descriptor; the rest is shape-agnostic.\n */\nexport function payloadFromHttpException(\n desc: GenericHttpExceptionDescriptor,\n): PafiErrorPayload {\n return normalizeHttpExceptionBody(desc);\n}\n\n/**\n * Normalize a generic `Error` (not an HttpException) — TypeORM\n * `QueryFailedError`, viem revert errors, etc. Returns a redacted\n * 500-class payload with no message bleed-through unless the error\n * is recognizably benign.\n */\nexport function payloadFromGenericError(err: Error): PafiErrorPayload {\n const name = err.name || \"INTERNAL_SERVER_ERROR\";\n const isDbError = name === \"QueryFailedError\" || name === \"EntityNotFoundError\";\n return {\n type: \"server_error\",\n code: name,\n message: isDbError\n ? sanitizeDbErrorMessage(err.message)\n : err.message || \"An unexpected error occurred\",\n safeToRetry: false,\n };\n}\n"],"mappings":"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","names":["import_core","buildSdkErrorBody","err","type","defaultErrorTypeForStatus","SDK_ERROR_HTTP_STATUS_CODE","httpStatus","body","code","message","safeToRetry","param","metadata","details","undefined","isValidationPipeBody","body","Array","isArray","extractParamFromValidatorMessage","message","idx","indexOf","undefined","candidate","slice","test","normalizeValidationPipeBody","fields","msg","param","key","push","fieldKeys","Object","keys","filter","k","length","payload","type","code","join","safeToRetry","metadata","fieldErrors","normalizeHttpExceptionBody","desc","statusCode","responseBody","exceptionName","fallbackMessage","defaultType","defaultErrorTypeForStatus","details","payloadFromPafiSdkError","err","buildSdkErrorBody","sanitizeDbErrorMessage","buildErrorEnvelope","input","now","ctx","Date","success","status","error","meta","timestamp","toISOString","requestId","path","payloadFromHttpException","payloadFromGenericError","name","isDbError"]}
|
package/dist/http/index.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
payloadFromGenericError,
|
|
5
5
|
payloadFromHttpException,
|
|
6
6
|
payloadFromPafiSdkError
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-RNQQYJIB.js";
|
|
8
|
+
import "../chunk-7QVYU63E.js";
|
|
9
9
|
export {
|
|
10
10
|
buildErrorEnvelope,
|
|
11
11
|
payloadFromGenericError,
|