@stacknet/userutils 0.6.1 → 0.6.3

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.
@@ -1,359 +0,0 @@
1
- import { S as Session } from '../auth-c1d7Eji2.js';
2
- import { e as ServerConfig, I as IPExtractorConfig } from '../config-xNca5ufB.js';
3
- export { f as decodeJWTPayload, g as extractIP, h as generateToken, m as maybeRefreshJWT, s as signJWT, v as verifyJWT, i as verifyJWTSignature } from '../config-xNca5ufB.js';
4
-
5
- /**
6
- * Server-only session type that includes the JWT.
7
- * NEVER export this from the client bundle.
8
- */
9
- interface ServerSession extends Session {
10
- jwt: string;
11
- }
12
-
13
- /** Rate limiter interface — implement with Redis, Upstash, or use the in-memory default */
14
- interface RateLimiter {
15
- check(key: string): Promise<{
16
- allowed: boolean;
17
- remaining: number;
18
- retryAfter?: number;
19
- }>;
20
- }
21
- /** Replay store interface — for preventing JWT re-sign replay attacks */
22
- interface ReplayStore {
23
- has(key: string): Promise<boolean>;
24
- set(key: string, ttlSeconds: number): Promise<void>;
25
- }
26
- /**
27
- * In-memory rate limiter with sliding window.
28
- *
29
- * Safe default for single-process deployments. For multi-process or
30
- * distributed environments, provide a Redis-backed RateLimiter instead.
31
- */
32
- declare function createInMemoryRateLimiter(opts: {
33
- maxRequests: number;
34
- windowMs: number;
35
- }): RateLimiter;
36
- /** In-memory replay store (single-process, entries auto-expire) */
37
- declare function createInMemoryReplayStore(): ReplayStore;
38
-
39
- interface AuthCallbackOptions {
40
- rateLimiter?: RateLimiter;
41
- }
42
- /**
43
- * Factory: POST handler for auth callback (login completion).
44
- *
45
- * Accepts wallet signature verification or OTP results from client,
46
- * validates with StackNet, sets HttpOnly JWT cookie + public session cookie + CSRF cookie.
47
- */
48
- declare function createAuthCallback(config: ServerConfig, opts?: AuthCallbackOptions): (request: Request) => Promise<Response>;
49
-
50
- /**
51
- * Factory: POST handler for logout.
52
- * Revokes session with StackNet and clears all auth cookies.
53
- */
54
- declare function createLogoutHandler(config: Pick<ServerConfig, 'stacknetUrl' | 'secureCookies' | 'cookieDomain'> & {
55
- /**
56
- * HMAC secret used to verify the JWT signature before extracting the
57
- * sessionId for upstream revocation. STRONGLY RECOMMENDED.
58
- *
59
- * Without this, the handler skips upstream revocation entirely and only
60
- * clears cookies — because trusting an unverified JWT for the sessionId
61
- * would let an attacker plant a forged cookie and trigger DELETE on
62
- * another user's session.
63
- */
64
- authSecret?: string;
65
- }): (request: Request) => Promise<Response>;
66
-
67
- /**
68
- * Factory: GET handler for session validation.
69
- * Reads HttpOnly JWT cookie, validates, returns public session info.
70
- * Transparently refreshes JWT if close to expiry.
71
- */
72
- declare function createSessionHandler(config: Pick<ServerConfig, 'authSecret' | 'jwtExpiry' | 'secureCookies' | 'cookieDomain' | 'sessionMaxAge'>): (request: Request) => Promise<Response>;
73
-
74
- interface OTPHandlerConfig extends Pick<ServerConfig, 'authSecret' | 'secureCookies' | 'cookieDomain' | 'sessionMaxAge' | 'jwtExpiry'> {
75
- /** The OTP secret to validate against */
76
- otpSecret: string;
77
- /** Rate limiter (default: 5 attempts per 5 min per IP) */
78
- rateLimiter?: RateLimiter;
79
- /** How to extract the real client IP for rate-limit keys. Defaults to
80
- * `{ trustedProxyCount: 1 }`. Set `trustedProxyCount: 0` if the handler
81
- * is exposed directly (no proxy in front). */
82
- ipConfig?: IPExtractorConfig;
83
- }
84
- /**
85
- * Factory: POST handler for OTP verification.
86
- * Validates OTP code, creates session JWT, sets HttpOnly cookie.
87
- */
88
- declare function createOTPHandler(config: OTPHandlerConfig): (request: Request) => Promise<Response>;
89
-
90
- interface OAuthHandlerConfig {
91
- rateLimiter?: RateLimiter;
92
- }
93
- /**
94
- * Factory: OAuth flow handlers.
95
- *
96
- * Returns two handlers:
97
- * - GET /api/auth/oauth/[provider] — Starts OAuth flow, returns redirect URL
98
- * - POST /api/auth/oauth/[provider]/callback — Handles OAuth callback, sets cookies
99
- */
100
- declare function createOAuthHandlers(config: ServerConfig, opts?: OAuthHandlerConfig): {
101
- startFlow: (request: Request) => Promise<Response>;
102
- handleCallback: (request: Request) => Promise<Response>;
103
- };
104
-
105
- interface GoogleOneTapHandlerConfig {
106
- rateLimiter?: RateLimiter;
107
- }
108
- /**
109
- * Factory: POST handler for Google One Tap credential verification.
110
- *
111
- * Receives the Google JWT credential from the client, verifies it with
112
- * Google's tokeninfo endpoint, then creates a StackNet session.
113
- */
114
- declare function createGoogleOneTapHandler(config: ServerConfig, opts?: GoogleOneTapHandlerConfig): (request: Request) => Promise<Response>;
115
-
116
- interface BillingProxyConfig extends Pick<ServerConfig, 'authSecret' | 'stacknetUrl' | 'stackId' | 'stacknetJwtSecret' | 'secureCookies' | 'cookieDomain' | 'sessionMaxAge' | 'jwtExpiry'> {
117
- /** Rate limiter for mutations (default: 20/min per user) */
118
- rateLimiter?: RateLimiter;
119
- /**
120
- * Canonical absolute origin (e.g. "https://app.example.com") used when
121
- * constructing Stripe success/cancel URLs. STRONGLY RECOMMENDED.
122
- *
123
- * Without this, the origin is derived from the request URL — which is
124
- * populated from the `Host` header. If the app is deployed behind a
125
- * proxy that does not validate / rewrite `Host`, an attacker can send
126
- * `Host: evil.example` and the post-checkout redirect will point there.
127
- * Stripe's dashboard allowlist catches most cases, but we should not
128
- * depend on that alone.
129
- *
130
- * Must be a full http(s) origin with no path. Factory throws on
131
- * malformed input so misconfigurations surface at boot, not at runtime.
132
- */
133
- canonicalOrigin?: string;
134
- }
135
- type Handler = (request: Request) => Promise<Response>;
136
- /**
137
- * Factory: creates all billing route handlers that proxy to StackNet.
138
- *
139
- * All POST handlers validate CSRF tokens. All handlers validate the JWT cookie
140
- * and transparently refresh if close to expiry.
141
- */
142
- declare function createBillingProxy(config: BillingProxyConfig): {
143
- plans: {
144
- GET: Handler;
145
- };
146
- subscription: {
147
- GET: Handler;
148
- };
149
- subscribe: {
150
- POST: Handler;
151
- };
152
- cancel: {
153
- POST: Handler;
154
- };
155
- usage: {
156
- GET: Handler;
157
- };
158
- history: {
159
- GET: Handler;
160
- };
161
- prepaid: {
162
- POST: Handler;
163
- };
164
- verifyPrepaid: {
165
- POST: Handler;
166
- };
167
- verifySession: {
168
- POST: Handler;
169
- };
170
- subscribeSol: {
171
- POST: Handler;
172
- };
173
- prepaidSol: {
174
- POST: Handler;
175
- };
176
- topup: {
177
- POST: Handler;
178
- };
179
- };
180
-
181
- /**
182
- * Factory: POST handler for Stripe webhooks.
183
- * Forwards raw body + stripe-signature to StackNet for verification and processing.
184
- */
185
- declare function createWebhookHandler(config: Pick<ServerConfig, 'stacknetUrl' | 'stackId'>): (request: Request) => Promise<Response>;
186
-
187
- interface CSRFConfig {
188
- /** Cookie name (default: '__csrf') */
189
- cookieName?: string;
190
- /** Header name (default: 'x-csrf-token') */
191
- headerName?: string;
192
- /** Token length in bytes (default: 32) */
193
- tokenLength?: number;
194
- /** Use Secure flag on cookie (default: true) */
195
- secure?: boolean;
196
- }
197
- /**
198
- * Create CSRF protection using the double-submit cookie pattern.
199
- *
200
- * 1. Server sets a non-HttpOnly cookie with a random token
201
- * 2. Client reads the cookie and sends the token in a header on mutations
202
- * 3. Server validates cookie === header (attacker can't read cookie cross-origin)
203
- */
204
- declare function createCSRFProtection(config?: CSRFConfig): {
205
- /**
206
- * Generate a CSRF token and add Set-Cookie header to a response.
207
- * Call this on auth callback (login) to establish the CSRF cookie.
208
- */
209
- generateToken(headers: Headers): string;
210
- /**
211
- * Validate a request's CSRF token (cookie vs header).
212
- * Returns true if valid, false if not.
213
- */
214
- validateRequest(request: Request): {
215
- valid: boolean;
216
- error?: string;
217
- };
218
- /** Cookie name for client-side reading */
219
- cookieName: string;
220
- /** Header name for client-side sending */
221
- headerName: string;
222
- };
223
-
224
- /** Standard security response headers */
225
- declare function securityHeaders(): Record<string, string>;
226
- /**
227
- * Wrap a request handler to add security headers to the response.
228
- */
229
- declare function withSecurityHeaders(handler: (request: Request) => Promise<Response> | Response): (request: Request) => Promise<Response>;
230
- /**
231
- * Generate security headers config for Next.js next.config.ts.
232
- *
233
- * Usage in next.config.ts:
234
- * ```ts
235
- * import { nextSecurityHeaders } from '@stacknet/userutils/server';
236
- * export default { headers: () => [{ source: '/(.*)', headers: nextSecurityHeaders() }] };
237
- * ```
238
- */
239
- declare function nextSecurityHeaders(): Array<{
240
- key: string;
241
- value: string;
242
- }>;
243
-
244
- /**
245
- * Re-sign a JWT using StackNet's HMAC-SHA256 scheme.
246
- *
247
- * Geoff apps sign JWTs with AUTH_SECRET. StackNet validates with JWT_SECRET.
248
- * This function re-signs using the StackNet secret so the backend resolves
249
- * the correct per-user identity.
250
- */
251
- declare function resignForStackNet(jwt: string, stacknetJwtSecret: string): string | null;
252
- /**
253
- * Build headers for proxying a user-scoped request to StackNet.
254
- * Re-signs the JWT so StackNet recognises the user's identity.
255
- *
256
- * Refuses to interpolate any value that doesn't match the compact JWT
257
- * format (header.body.signature, base64url segments only) to prevent
258
- * header injection.
259
- */
260
- declare function buildStackNetHeaders(jwt: string, stacknetJwtSecret: string): Record<string, string>;
261
- /**
262
- * Extract JWT from a request's cookies or Authorization header.
263
- */
264
- declare function extractJwt(request: Request): string | null;
265
-
266
- /**
267
- * Server-side proxy helpers for StackNet preview codes.
268
- *
269
- * Preview codes are admin-minted 6-digit access credentials with a
270
- * per-code token budget. Only the pinned admin global id (enforced
271
- * by StackNet's state machine) can mint / list / revoke codes.
272
- *
273
- * These helpers wrap `fetch` against the StackNet HTTP layer with
274
- * re-signed JWT cookies (same pattern as `buildStackNetHeaders`).
275
- * Admin-console API routes are expected to call them; the raw
276
- * endpoints are NOT exposed in the client bundle.
277
- */
278
- interface PreviewCode {
279
- code: string;
280
- createdBy: string;
281
- tokenBudget: number;
282
- tokensUsed: number;
283
- tokensRemaining: number;
284
- createdAt: number;
285
- expiresAt: number | null;
286
- revoked: boolean;
287
- /** Optional human-readable label (e.g. "Tester: Alice"). Absent on
288
- * codes minted before the name field shipped. */
289
- name?: string | null;
290
- }
291
- interface MintPreviewCodeOptions {
292
- /** Token budget for the new code. Must be > 0. */
293
- tokenBudget: number;
294
- /** Optional explicit 6-digit code string. Server generates one
295
- * if omitted. */
296
- code?: string;
297
- /** Optional Unix-ms expiry. */
298
- expiresAt?: number;
299
- /** Optional human-readable label for the code. Shown in the admin
300
- * list so the operator can tell codes apart. */
301
- name?: string;
302
- }
303
- interface PreviewCodesProxyConfig {
304
- /** StackNet base URL (no trailing slash). */
305
- stacknetBaseUrl: string;
306
- /** Shared HMAC secret for re-signing the caller's JWT. */
307
- stacknetJwtSecret: string;
308
- /** Caller's StackAuth JWT (user identity). */
309
- jwt: string;
310
- }
311
- /** Admin-only: mint a new preview code. Returns the new code row. */
312
- declare function mintPreviewCode(cfg: PreviewCodesProxyConfig, options: MintPreviewCodeOptions): Promise<{
313
- minted: boolean;
314
- code: PreviewCode;
315
- } | {
316
- error: string;
317
- status: number;
318
- }>;
319
- /** Admin-only: list every preview code in the system. */
320
- declare function listPreviewCodes(cfg: PreviewCodesProxyConfig): Promise<PreviewCode[] | {
321
- error: string;
322
- status: number;
323
- }>;
324
- /** Public: read a code's balance + status. Used by auth middleware. */
325
- declare function getPreviewCode(stacknetBaseUrl: string, code: string): Promise<PreviewCode | null>;
326
- /** Admin-only: revoke a preview code. */
327
- declare function revokePreviewCode(cfg: PreviewCodesProxyConfig, code: string): Promise<{
328
- revoked: boolean;
329
- code: PreviewCode;
330
- } | {
331
- error: string;
332
- status: number;
333
- }>;
334
- /** Internal: debit tokens from a preview code. Called by the metering
335
- * layer after inference completes. */
336
- declare function redeemPreviewCode(stacknetBaseUrl: string, code: string, tokens: number): Promise<{
337
- redeemed: boolean;
338
- code: string;
339
- tokensUsed: number;
340
- tokensRemaining: number;
341
- } | {
342
- error: string;
343
- status: number;
344
- }>;
345
- /** Allowlist of admin global ids that can mint / revoke preview
346
- * codes. Mirrors PREVIEW_CODE_ADMIN_GLOBAL_IDS in the Rust state
347
- * machine — must stay in sync. Admin-console UI and API route guards
348
- * should call `isPreviewCodeAdmin(currentUser.userId)` instead of
349
- * comparing against a single constant so every entry is accepted. */
350
- declare const PREVIEW_CODE_ADMIN_GLOBAL_IDS: readonly string[];
351
- /** Returns true if the given global id is in the preview-code admin
352
- * allowlist. */
353
- declare function isPreviewCodeAdmin(globalId: string | null | undefined): boolean;
354
- /** Back-compat alias for callers that only need a single canonical
355
- * admin id for display/logging. Don't use for gating — use
356
- * `isPreviewCodeAdmin()` to accept every entry in the allowlist. */
357
- declare const PREVIEW_CODE_ADMIN_GLOBAL_ID: string;
358
-
359
- export { type AuthCallbackOptions, type BillingProxyConfig, type CSRFConfig, type GoogleOneTapHandlerConfig, IPExtractorConfig, type MintPreviewCodeOptions, type OAuthHandlerConfig, type OTPHandlerConfig, PREVIEW_CODE_ADMIN_GLOBAL_ID, PREVIEW_CODE_ADMIN_GLOBAL_IDS, type PreviewCode, type PreviewCodesProxyConfig, type RateLimiter, type ReplayStore, ServerConfig, type ServerSession, buildStackNetHeaders, createAuthCallback, createBillingProxy, createCSRFProtection, createGoogleOneTapHandler, createInMemoryRateLimiter, createInMemoryReplayStore, createLogoutHandler, createOAuthHandlers, createOTPHandler, createSessionHandler, createWebhookHandler, extractJwt, getPreviewCode, isPreviewCodeAdmin, listPreviewCodes, mintPreviewCode, nextSecurityHeaders, redeemPreviewCode, resignForStackNet, revokePreviewCode, securityHeaders, withSecurityHeaders };