@stacknet/userutils 0.6.1 → 0.6.2

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,7 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { d as UserUtilsConfig, c as UserUtilsCallbacks } from '../config-CLzVWDrU.cjs';
4
- import { ProfileScope } from '../types/index.cjs';
3
+ import { UserUtilsConfig, UserUtilsCallbacks, ProfileScope } from '../types/index.cjs';
5
4
  import '../auth-c1d7Eji2.cjs';
6
5
 
7
6
  interface UserUtilsContextValue {
@@ -1,7 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { d as UserUtilsConfig, c as UserUtilsCallbacks } from '../config-xNca5ufB.js';
4
- import { ProfileScope } from '../types/index.js';
3
+ import { UserUtilsConfig, UserUtilsCallbacks, ProfileScope } from '../types/index.js';
5
4
  import '../auth-c1d7Eji2.js';
6
5
 
7
6
  interface UserUtilsContextValue {
@@ -1,6 +1,5 @@
1
1
  import { P as PublicSession, W as Web3Chain } from '../auth-c1d7Eji2.cjs';
2
- import { d as UserUtilsConfig, B as BillingPlan, S as Subscription, U as UsageSummary, b as PrepaidVerifyResult, a as BillingRecord } from '../config-CLzVWDrU.cjs';
3
- import { ProfileScope, UserProfile, ProfileUpdateInput } from '../types/index.cjs';
2
+ import { UserUtilsConfig, BillingPlan, Subscription, UsageSummary, PrepaidVerifyResult, BillingRecord, ProfileScope, UserProfile, ProfileUpdateInput } from '../types/index.cjs';
4
3
 
5
4
  /**
6
5
  * Hook to read the public session cookie for UI state.
@@ -1,6 +1,5 @@
1
1
  import { P as PublicSession, W as Web3Chain } from '../auth-c1d7Eji2.js';
2
- import { d as UserUtilsConfig, B as BillingPlan, S as Subscription, U as UsageSummary, b as PrepaidVerifyResult, a as BillingRecord } from '../config-xNca5ufB.js';
3
- import { ProfileScope, UserProfile, ProfileUpdateInput } from '../types/index.js';
2
+ import { UserUtilsConfig, BillingPlan, Subscription, UsageSummary, PrepaidVerifyResult, BillingRecord, ProfileScope, UserProfile, ProfileUpdateInput } from '../types/index.js';
4
3
 
5
4
  /**
6
5
  * Hook to read the public session cookie for UI state.
package/dist/index.d.cts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { A as APIResponse, N as NetworkStatus, W as Web3Chain } from './auth-c1d7Eji2.cjs';
2
2
  export { M as MPCNode, P as PublicSession, S as Session } from './auth-c1d7Eji2.cjs';
3
- export { B as BillingPlan, a as BillingRecord, P as PrepaidCheckoutResult, b as PrepaidVerifyResult, S as Subscription, U as UsageSummary, c as UserUtilsCallbacks, d as UserUtilsConfig } from './config-CLzVWDrU.cjs';
4
- export { ProfileScope, ProfileUpdateInput, UserProfile } from './types/index.cjs';
3
+ export { BillingPlan, BillingRecord, PrepaidCheckoutResult, PrepaidVerifyResult, ProfileScope, ProfileUpdateInput, Subscription, UsageSummary, UserProfile, UserUtilsCallbacks, UserUtilsConfig } from './types/index.cjs';
5
4
  export { cn, decodeJwtPayloadClient, formatTokens, readCSRFCookie, readSessionCookie, safeUrl, validateRedirectUrl } from './utils/index.cjs';
6
5
  export { BridgeIdentity, GoogleOneTapConfig, StackPublicConfig, UseJoinCodeConfig, UseJoinCodeResult, WalletState, useAuthBridge, useBillingHistory, useCSRFToken, useGoogleOneTap, useJoinCode, usePlans, usePrepaidCheckout, useProfile, useSession, useStackAuth, useStackConfig, useSubscription, useUsage, useWeb3Wallet } from './hooks/index.cjs';
7
6
  export { AppleIcon, ConnectWidget, ConnectWidgetProps, DiscordIcon, EthereumIcon, GoogleIcon, MetaMaskIcon, OTPInput, OTPInputProps, PhantomIcon, ProfileSettings, ProfileSettingsProps, SolanaIcon, TelegramIcon, TwitterIcon, UserUtilsProvider, UserUtilsProviderProps, useUserUtilsContext } from './components/index.cjs';
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { A as APIResponse, N as NetworkStatus, W as Web3Chain } from './auth-c1d7Eji2.js';
2
2
  export { M as MPCNode, P as PublicSession, S as Session } from './auth-c1d7Eji2.js';
3
- export { B as BillingPlan, a as BillingRecord, P as PrepaidCheckoutResult, b as PrepaidVerifyResult, S as Subscription, U as UsageSummary, c as UserUtilsCallbacks, d as UserUtilsConfig } from './config-xNca5ufB.js';
4
- export { ProfileScope, ProfileUpdateInput, UserProfile } from './types/index.js';
3
+ export { BillingPlan, BillingRecord, PrepaidCheckoutResult, PrepaidVerifyResult, ProfileScope, ProfileUpdateInput, Subscription, UsageSummary, UserProfile, UserUtilsCallbacks, UserUtilsConfig } from './types/index.js';
5
4
  export { cn, decodeJwtPayloadClient, formatTokens, readCSRFCookie, readSessionCookie, safeUrl, validateRedirectUrl } from './utils/index.js';
6
5
  export { BridgeIdentity, GoogleOneTapConfig, StackPublicConfig, UseJoinCodeConfig, UseJoinCodeResult, WalletState, useAuthBridge, useBillingHistory, useCSRFToken, useGoogleOneTap, useJoinCode, usePlans, usePrepaidCheckout, useProfile, useSession, useStackAuth, useStackConfig, useSubscription, useUsage, useWeb3Wallet } from './hooks/index.js';
7
6
  export { AppleIcon, ConnectWidget, ConnectWidgetProps, DiscordIcon, EthereumIcon, GoogleIcon, MetaMaskIcon, OTPInput, OTPInputProps, PhantomIcon, ProfileSettings, ProfileSettingsProps, SolanaIcon, TelegramIcon, TwitterIcon, UserUtilsProvider, UserUtilsProviderProps, useUserUtilsContext } from './components/index.js';
@@ -1,5 +1,75 @@
1
- export { A as APIResponse, M as MPCNode, N as NetworkStatus, P as PublicSession, S as Session, W as Web3Chain } from '../auth-c1d7Eji2.cjs';
2
- export { B as BillingPlan, a as BillingRecord, P as PrepaidCheckoutResult, b as PrepaidVerifyResult, S as Subscription, U as UsageSummary, c as UserUtilsCallbacks, d as UserUtilsConfig } from '../config-CLzVWDrU.cjs';
1
+ import { P as PublicSession } from '../auth-c1d7Eji2.cjs';
2
+ export { A as APIResponse, M as MPCNode, N as NetworkStatus, S as Session, W as Web3Chain } from '../auth-c1d7Eji2.cjs';
3
+
4
+ interface BillingPlan {
5
+ id: string;
6
+ name: string;
7
+ price_cents: number;
8
+ token_allocation: number;
9
+ features: string;
10
+ sort_order?: number;
11
+ is_active?: boolean;
12
+ }
13
+ interface Subscription {
14
+ plan?: {
15
+ id: string;
16
+ name: string;
17
+ priceCents: number;
18
+ tokenAllocation: number;
19
+ };
20
+ planId?: string;
21
+ planName?: string;
22
+ status: string;
23
+ tokensUsed?: number;
24
+ tokensRemaining?: number;
25
+ usagePercent?: number;
26
+ periodStart?: number;
27
+ periodEnd?: number;
28
+ cancelAtPeriodEnd?: boolean;
29
+ stripeCustomerId?: string;
30
+ }
31
+ interface UsageSummary {
32
+ ownerId: string;
33
+ planAllocation: number;
34
+ planId: string | null;
35
+ planName: string;
36
+ inferenceUsed: number;
37
+ ledgerSpent: number;
38
+ totalUsed: number;
39
+ remaining: number;
40
+ percent: number;
41
+ exceeded: boolean;
42
+ breakdown: {
43
+ subscription: number;
44
+ prepaidCredit: number;
45
+ tokenGrants: number;
46
+ inference: number;
47
+ skillRegistrations: number;
48
+ skillRegistrationCount: number;
49
+ };
50
+ }
51
+ interface PrepaidCheckoutResult {
52
+ url: string;
53
+ sessionId?: string;
54
+ }
55
+ interface PrepaidVerifyResult {
56
+ alreadyCredited: boolean;
57
+ tokensCredited?: number;
58
+ amountCents?: number;
59
+ paymentRef?: string;
60
+ }
61
+ interface BillingRecord {
62
+ id: string;
63
+ recorded_at?: number;
64
+ date?: string;
65
+ reason?: string;
66
+ description?: string;
67
+ amount: number;
68
+ direction?: string;
69
+ source?: string;
70
+ status?: string;
71
+ payment_ref?: string;
72
+ }
3
73
 
4
74
  /** User profile — global (network-wide) or stack-scoped */
5
75
  interface UserProfile {
@@ -33,4 +103,23 @@ type ProfileScope = 'global' | {
33
103
  stackId: string;
34
104
  };
35
105
 
36
- export type { ProfileScope, ProfileUpdateInput, UserProfile };
106
+ /** Client-side configuration for UserUtilsProvider */
107
+ interface UserUtilsConfig {
108
+ /** Base URL for API calls (empty string = same origin) */
109
+ apiBaseUrl: string;
110
+ /** StackNet stack identifier */
111
+ stackId?: string;
112
+ /** Direct StackNet URL for client-side API calls (e.g. challenges) */
113
+ stacknetUrl?: string;
114
+ /** Theme preference */
115
+ theme?: 'light' | 'dark' | 'system';
116
+ }
117
+ /** Callbacks for auth and billing events */
118
+ interface UserUtilsCallbacks {
119
+ onAuthSuccess?: (session: PublicSession) => void;
120
+ onAuthError?: (error: Error) => void;
121
+ onLogout?: () => void;
122
+ onSubscriptionChange?: (subscription: Subscription) => void;
123
+ }
124
+
125
+ export { type BillingPlan, type BillingRecord, type PrepaidCheckoutResult, type PrepaidVerifyResult, type ProfileScope, type ProfileUpdateInput, PublicSession, type Subscription, type UsageSummary, type UserProfile, type UserUtilsCallbacks, type UserUtilsConfig };
@@ -1,5 +1,75 @@
1
- export { A as APIResponse, M as MPCNode, N as NetworkStatus, P as PublicSession, S as Session, W as Web3Chain } from '../auth-c1d7Eji2.js';
2
- export { B as BillingPlan, a as BillingRecord, P as PrepaidCheckoutResult, b as PrepaidVerifyResult, S as Subscription, U as UsageSummary, c as UserUtilsCallbacks, d as UserUtilsConfig } from '../config-xNca5ufB.js';
1
+ import { P as PublicSession } from '../auth-c1d7Eji2.js';
2
+ export { A as APIResponse, M as MPCNode, N as NetworkStatus, S as Session, W as Web3Chain } from '../auth-c1d7Eji2.js';
3
+
4
+ interface BillingPlan {
5
+ id: string;
6
+ name: string;
7
+ price_cents: number;
8
+ token_allocation: number;
9
+ features: string;
10
+ sort_order?: number;
11
+ is_active?: boolean;
12
+ }
13
+ interface Subscription {
14
+ plan?: {
15
+ id: string;
16
+ name: string;
17
+ priceCents: number;
18
+ tokenAllocation: number;
19
+ };
20
+ planId?: string;
21
+ planName?: string;
22
+ status: string;
23
+ tokensUsed?: number;
24
+ tokensRemaining?: number;
25
+ usagePercent?: number;
26
+ periodStart?: number;
27
+ periodEnd?: number;
28
+ cancelAtPeriodEnd?: boolean;
29
+ stripeCustomerId?: string;
30
+ }
31
+ interface UsageSummary {
32
+ ownerId: string;
33
+ planAllocation: number;
34
+ planId: string | null;
35
+ planName: string;
36
+ inferenceUsed: number;
37
+ ledgerSpent: number;
38
+ totalUsed: number;
39
+ remaining: number;
40
+ percent: number;
41
+ exceeded: boolean;
42
+ breakdown: {
43
+ subscription: number;
44
+ prepaidCredit: number;
45
+ tokenGrants: number;
46
+ inference: number;
47
+ skillRegistrations: number;
48
+ skillRegistrationCount: number;
49
+ };
50
+ }
51
+ interface PrepaidCheckoutResult {
52
+ url: string;
53
+ sessionId?: string;
54
+ }
55
+ interface PrepaidVerifyResult {
56
+ alreadyCredited: boolean;
57
+ tokensCredited?: number;
58
+ amountCents?: number;
59
+ paymentRef?: string;
60
+ }
61
+ interface BillingRecord {
62
+ id: string;
63
+ recorded_at?: number;
64
+ date?: string;
65
+ reason?: string;
66
+ description?: string;
67
+ amount: number;
68
+ direction?: string;
69
+ source?: string;
70
+ status?: string;
71
+ payment_ref?: string;
72
+ }
3
73
 
4
74
  /** User profile — global (network-wide) or stack-scoped */
5
75
  interface UserProfile {
@@ -33,4 +103,23 @@ type ProfileScope = 'global' | {
33
103
  stackId: string;
34
104
  };
35
105
 
36
- export type { ProfileScope, ProfileUpdateInput, UserProfile };
106
+ /** Client-side configuration for UserUtilsProvider */
107
+ interface UserUtilsConfig {
108
+ /** Base URL for API calls (empty string = same origin) */
109
+ apiBaseUrl: string;
110
+ /** StackNet stack identifier */
111
+ stackId?: string;
112
+ /** Direct StackNet URL for client-side API calls (e.g. challenges) */
113
+ stacknetUrl?: string;
114
+ /** Theme preference */
115
+ theme?: 'light' | 'dark' | 'system';
116
+ }
117
+ /** Callbacks for auth and billing events */
118
+ interface UserUtilsCallbacks {
119
+ onAuthSuccess?: (session: PublicSession) => void;
120
+ onAuthError?: (error: Error) => void;
121
+ onLogout?: () => void;
122
+ onSubscriptionChange?: (subscription: Subscription) => void;
123
+ }
124
+
125
+ export { type BillingPlan, type BillingRecord, type PrepaidCheckoutResult, type PrepaidVerifyResult, type ProfileScope, type ProfileUpdateInput, PublicSession, type Subscription, type UsageSummary, type UserProfile, type UserUtilsCallbacks, type UserUtilsConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stacknet/userutils",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Reusable auth, billing, and security utilities for StackNet stacks and applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1,177 +0,0 @@
1
- import { P as PublicSession } from './auth-c1d7Eji2.cjs';
2
-
3
- interface BillingPlan {
4
- id: string;
5
- name: string;
6
- price_cents: number;
7
- token_allocation: number;
8
- features: string;
9
- sort_order?: number;
10
- is_active?: boolean;
11
- }
12
- interface Subscription {
13
- plan?: {
14
- id: string;
15
- name: string;
16
- priceCents: number;
17
- tokenAllocation: number;
18
- };
19
- planId?: string;
20
- planName?: string;
21
- status: string;
22
- tokensUsed?: number;
23
- tokensRemaining?: number;
24
- usagePercent?: number;
25
- periodStart?: number;
26
- periodEnd?: number;
27
- cancelAtPeriodEnd?: boolean;
28
- stripeCustomerId?: string;
29
- }
30
- interface UsageSummary {
31
- ownerId: string;
32
- planAllocation: number;
33
- planId: string | null;
34
- planName: string;
35
- inferenceUsed: number;
36
- ledgerSpent: number;
37
- totalUsed: number;
38
- remaining: number;
39
- percent: number;
40
- exceeded: boolean;
41
- breakdown: {
42
- subscription: number;
43
- prepaidCredit: number;
44
- tokenGrants: number;
45
- inference: number;
46
- skillRegistrations: number;
47
- skillRegistrationCount: number;
48
- };
49
- }
50
- interface PrepaidCheckoutResult {
51
- url: string;
52
- sessionId?: string;
53
- }
54
- interface PrepaidVerifyResult {
55
- alreadyCredited: boolean;
56
- tokensCredited?: number;
57
- amountCents?: number;
58
- paymentRef?: string;
59
- }
60
- interface BillingRecord {
61
- id: string;
62
- recorded_at?: number;
63
- date?: string;
64
- reason?: string;
65
- description?: string;
66
- amount: number;
67
- direction?: string;
68
- source?: string;
69
- status?: string;
70
- payment_ref?: string;
71
- }
72
-
73
- /** Decode JWT payload without verification (server-side helper) */
74
- declare function decodeJWTPayload(jwt: string): Record<string, any> | null;
75
- /** Sign a JWT with HMAC-SHA256 */
76
- declare function signJWT(payload: Record<string, any>, secret: string): string;
77
- /** Verify a JWT signature with HMAC-SHA256 (constant-time comparison) */
78
- declare function verifyJWTSignature(jwt: string, secret: string): boolean;
79
- /** Verify JWT and return payload if valid (checks signature + expiry) */
80
- declare function verifyJWT(jwt: string, secret: string): Record<string, any> | null;
81
- /**
82
- * Check if JWT needs refresh and return a new one if so.
83
- * Returns null if no refresh needed or JWT is invalid.
84
- */
85
- declare function maybeRefreshJWT(jwt: string, secret: string, expirySeconds?: number, refreshWindowSeconds?: number): string | null;
86
- /** Generate a cryptographically secure random token */
87
- declare function generateToken(bytes?: number): string;
88
- /** Options controlling how the real client IP is extracted from a Request.
89
- *
90
- * The previous implementation trusted `X-Forwarded-For[0]` unconditionally,
91
- * which lets any direct caller pin arbitrary values and bypass per-IP rate
92
- * limits. The correct read depends on how many reverse proxies sit between
93
- * the app and the real client: with N trusted hops, the real client IP is
94
- * at position `(length - N)` in the XFF list, because each trusted hop
95
- * appends its peer IP and the attacker-controlled prefix is pushed left. */
96
- interface IPExtractorConfig {
97
- /** Number of trusted reverse-proxy hops between this app and the real client.
98
- * - `0`: do NOT trust X-Forwarded-For (app is directly internet-exposed).
99
- * - `1` (default): one trusted proxy (e.g. single LB, Vercel edge, Nginx).
100
- * - `N`: N trusted hops; the real client IP is `XFF[length - N]`.
101
- * If XFF has fewer entries than expected, the chain is misconfigured or
102
- * spoofed and the extractor falls through to the next source. */
103
- trustedProxyCount?: number;
104
- /** Trust the `X-Real-IP` header. Only enable if your proxy sets it AND
105
- * strips any inbound `X-Real-IP` from clients. Default: `false`. */
106
- trustRealIpHeader?: boolean;
107
- /** Override entirely — return the real client IP from a request however
108
- * you know best (e.g. a platform-specific header like `cf-connecting-ip`
109
- * or `x-vercel-forwarded-for`). If this returns a non-empty string it
110
- * wins; if it returns null/empty the other strategies run. */
111
- customExtractor?: (request: Request) => string | null | undefined;
112
- }
113
- /** Extract the real client IP address from a request.
114
- *
115
- * Default behavior (`trustedProxyCount: 1`) is safe for the common case of
116
- * one trusted reverse proxy. Consumers with no proxy must pass `0`, and
117
- * consumers behind multiple proxies must pass the exact hop count. */
118
- declare function extractIP(request: Request, config?: IPExtractorConfig): string;
119
-
120
- /** Client-side configuration for UserUtilsProvider */
121
- interface UserUtilsConfig {
122
- /** Base URL for API calls (empty string = same origin) */
123
- apiBaseUrl: string;
124
- /** StackNet stack identifier */
125
- stackId?: string;
126
- /** Direct StackNet URL for client-side API calls (e.g. challenges) */
127
- stacknetUrl?: string;
128
- /** Theme preference */
129
- theme?: 'light' | 'dark' | 'system';
130
- }
131
- /** Callbacks for auth and billing events */
132
- interface UserUtilsCallbacks {
133
- onAuthSuccess?: (session: PublicSession) => void;
134
- onAuthError?: (error: Error) => void;
135
- onLogout?: () => void;
136
- onSubscriptionChange?: (subscription: Subscription) => void;
137
- }
138
- /** Server-side configuration for handler factories */
139
- interface ServerConfig {
140
- /** HMAC-SHA256 secret for signing JWTs */
141
- authSecret: string;
142
- /** StackNet backend URL (always https://stacknet.magma-rpc.com) */
143
- stacknetUrl: string;
144
- /** StackNet stack identifier */
145
- stackId: string;
146
- /** JWT secret for re-signing to StackNet (defaults to authSecret) */
147
- stacknetJwtSecret?: string;
148
- /** Cookie domain for subdomain sharing (e.g. '.geoff.ai') */
149
- cookieDomain?: string;
150
- /** Use Secure flag on cookies (default: true) */
151
- secureCookies?: boolean;
152
- /** Session max age in seconds (default: 604800 = 7 days) */
153
- sessionMaxAge?: number;
154
- /** JWT expiry in seconds (default: 900 = 15 minutes) */
155
- jwtExpiry?: number;
156
- /**
157
- * Google OAuth client ID (single). Used by createGoogleOneTapHandler to
158
- * validate the `aud` claim on incoming Google ID tokens. Required for
159
- * Google One Tap — without it, an ID token issued to any other Google
160
- * application could be replayed against this endpoint.
161
- */
162
- googleClientId?: string;
163
- /**
164
- * Google OAuth client IDs (multiple). Use when the stack accepts tokens
165
- * from more than one Google client (e.g. web + native).
166
- */
167
- googleClientIds?: string[];
168
- /**
169
- * How the handlers should derive the real client IP for rate-limit keys.
170
- * Defaults to `{ trustedProxyCount: 1 }`. Set `trustedProxyCount: 0` if
171
- * the app is exposed directly (no proxy) — otherwise any caller can pin
172
- * their X-Forwarded-For and bypass rate limiting.
173
- */
174
- ipConfig?: IPExtractorConfig;
175
- }
176
-
177
- export { type BillingPlan as B, type IPExtractorConfig as I, type PrepaidCheckoutResult as P, type Subscription as S, type UsageSummary as U, type BillingRecord as a, type PrepaidVerifyResult as b, type UserUtilsCallbacks as c, type UserUtilsConfig as d, type ServerConfig as e, decodeJWTPayload as f, extractIP as g, generateToken as h, verifyJWTSignature as i, maybeRefreshJWT as m, signJWT as s, verifyJWT as v };
@@ -1,177 +0,0 @@
1
- import { P as PublicSession } from './auth-c1d7Eji2.js';
2
-
3
- interface BillingPlan {
4
- id: string;
5
- name: string;
6
- price_cents: number;
7
- token_allocation: number;
8
- features: string;
9
- sort_order?: number;
10
- is_active?: boolean;
11
- }
12
- interface Subscription {
13
- plan?: {
14
- id: string;
15
- name: string;
16
- priceCents: number;
17
- tokenAllocation: number;
18
- };
19
- planId?: string;
20
- planName?: string;
21
- status: string;
22
- tokensUsed?: number;
23
- tokensRemaining?: number;
24
- usagePercent?: number;
25
- periodStart?: number;
26
- periodEnd?: number;
27
- cancelAtPeriodEnd?: boolean;
28
- stripeCustomerId?: string;
29
- }
30
- interface UsageSummary {
31
- ownerId: string;
32
- planAllocation: number;
33
- planId: string | null;
34
- planName: string;
35
- inferenceUsed: number;
36
- ledgerSpent: number;
37
- totalUsed: number;
38
- remaining: number;
39
- percent: number;
40
- exceeded: boolean;
41
- breakdown: {
42
- subscription: number;
43
- prepaidCredit: number;
44
- tokenGrants: number;
45
- inference: number;
46
- skillRegistrations: number;
47
- skillRegistrationCount: number;
48
- };
49
- }
50
- interface PrepaidCheckoutResult {
51
- url: string;
52
- sessionId?: string;
53
- }
54
- interface PrepaidVerifyResult {
55
- alreadyCredited: boolean;
56
- tokensCredited?: number;
57
- amountCents?: number;
58
- paymentRef?: string;
59
- }
60
- interface BillingRecord {
61
- id: string;
62
- recorded_at?: number;
63
- date?: string;
64
- reason?: string;
65
- description?: string;
66
- amount: number;
67
- direction?: string;
68
- source?: string;
69
- status?: string;
70
- payment_ref?: string;
71
- }
72
-
73
- /** Decode JWT payload without verification (server-side helper) */
74
- declare function decodeJWTPayload(jwt: string): Record<string, any> | null;
75
- /** Sign a JWT with HMAC-SHA256 */
76
- declare function signJWT(payload: Record<string, any>, secret: string): string;
77
- /** Verify a JWT signature with HMAC-SHA256 (constant-time comparison) */
78
- declare function verifyJWTSignature(jwt: string, secret: string): boolean;
79
- /** Verify JWT and return payload if valid (checks signature + expiry) */
80
- declare function verifyJWT(jwt: string, secret: string): Record<string, any> | null;
81
- /**
82
- * Check if JWT needs refresh and return a new one if so.
83
- * Returns null if no refresh needed or JWT is invalid.
84
- */
85
- declare function maybeRefreshJWT(jwt: string, secret: string, expirySeconds?: number, refreshWindowSeconds?: number): string | null;
86
- /** Generate a cryptographically secure random token */
87
- declare function generateToken(bytes?: number): string;
88
- /** Options controlling how the real client IP is extracted from a Request.
89
- *
90
- * The previous implementation trusted `X-Forwarded-For[0]` unconditionally,
91
- * which lets any direct caller pin arbitrary values and bypass per-IP rate
92
- * limits. The correct read depends on how many reverse proxies sit between
93
- * the app and the real client: with N trusted hops, the real client IP is
94
- * at position `(length - N)` in the XFF list, because each trusted hop
95
- * appends its peer IP and the attacker-controlled prefix is pushed left. */
96
- interface IPExtractorConfig {
97
- /** Number of trusted reverse-proxy hops between this app and the real client.
98
- * - `0`: do NOT trust X-Forwarded-For (app is directly internet-exposed).
99
- * - `1` (default): one trusted proxy (e.g. single LB, Vercel edge, Nginx).
100
- * - `N`: N trusted hops; the real client IP is `XFF[length - N]`.
101
- * If XFF has fewer entries than expected, the chain is misconfigured or
102
- * spoofed and the extractor falls through to the next source. */
103
- trustedProxyCount?: number;
104
- /** Trust the `X-Real-IP` header. Only enable if your proxy sets it AND
105
- * strips any inbound `X-Real-IP` from clients. Default: `false`. */
106
- trustRealIpHeader?: boolean;
107
- /** Override entirely — return the real client IP from a request however
108
- * you know best (e.g. a platform-specific header like `cf-connecting-ip`
109
- * or `x-vercel-forwarded-for`). If this returns a non-empty string it
110
- * wins; if it returns null/empty the other strategies run. */
111
- customExtractor?: (request: Request) => string | null | undefined;
112
- }
113
- /** Extract the real client IP address from a request.
114
- *
115
- * Default behavior (`trustedProxyCount: 1`) is safe for the common case of
116
- * one trusted reverse proxy. Consumers with no proxy must pass `0`, and
117
- * consumers behind multiple proxies must pass the exact hop count. */
118
- declare function extractIP(request: Request, config?: IPExtractorConfig): string;
119
-
120
- /** Client-side configuration for UserUtilsProvider */
121
- interface UserUtilsConfig {
122
- /** Base URL for API calls (empty string = same origin) */
123
- apiBaseUrl: string;
124
- /** StackNet stack identifier */
125
- stackId?: string;
126
- /** Direct StackNet URL for client-side API calls (e.g. challenges) */
127
- stacknetUrl?: string;
128
- /** Theme preference */
129
- theme?: 'light' | 'dark' | 'system';
130
- }
131
- /** Callbacks for auth and billing events */
132
- interface UserUtilsCallbacks {
133
- onAuthSuccess?: (session: PublicSession) => void;
134
- onAuthError?: (error: Error) => void;
135
- onLogout?: () => void;
136
- onSubscriptionChange?: (subscription: Subscription) => void;
137
- }
138
- /** Server-side configuration for handler factories */
139
- interface ServerConfig {
140
- /** HMAC-SHA256 secret for signing JWTs */
141
- authSecret: string;
142
- /** StackNet backend URL (always https://stacknet.magma-rpc.com) */
143
- stacknetUrl: string;
144
- /** StackNet stack identifier */
145
- stackId: string;
146
- /** JWT secret for re-signing to StackNet (defaults to authSecret) */
147
- stacknetJwtSecret?: string;
148
- /** Cookie domain for subdomain sharing (e.g. '.geoff.ai') */
149
- cookieDomain?: string;
150
- /** Use Secure flag on cookies (default: true) */
151
- secureCookies?: boolean;
152
- /** Session max age in seconds (default: 604800 = 7 days) */
153
- sessionMaxAge?: number;
154
- /** JWT expiry in seconds (default: 900 = 15 minutes) */
155
- jwtExpiry?: number;
156
- /**
157
- * Google OAuth client ID (single). Used by createGoogleOneTapHandler to
158
- * validate the `aud` claim on incoming Google ID tokens. Required for
159
- * Google One Tap — without it, an ID token issued to any other Google
160
- * application could be replayed against this endpoint.
161
- */
162
- googleClientId?: string;
163
- /**
164
- * Google OAuth client IDs (multiple). Use when the stack accepts tokens
165
- * from more than one Google client (e.g. web + native).
166
- */
167
- googleClientIds?: string[];
168
- /**
169
- * How the handlers should derive the real client IP for rate-limit keys.
170
- * Defaults to `{ trustedProxyCount: 1 }`. Set `trustedProxyCount: 0` if
171
- * the app is exposed directly (no proxy) — otherwise any caller can pin
172
- * their X-Forwarded-For and bypass rate limiting.
173
- */
174
- ipConfig?: IPExtractorConfig;
175
- }
176
-
177
- export { type BillingPlan as B, type IPExtractorConfig as I, type PrepaidCheckoutResult as P, type Subscription as S, type UsageSummary as U, type BillingRecord as a, type PrepaidVerifyResult as b, type UserUtilsCallbacks as c, type UserUtilsConfig as d, type ServerConfig as e, decodeJWTPayload as f, extractIP as g, generateToken as h, verifyJWTSignature as i, maybeRefreshJWT as m, signJWT as s, verifyJWT as v };
@@ -1,359 +0,0 @@
1
- import { S as Session } from '../auth-c1d7Eji2.cjs';
2
- import { e as ServerConfig, I as IPExtractorConfig } from '../config-CLzVWDrU.cjs';
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-CLzVWDrU.cjs';
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 };
@@ -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 };