@stacknet/userutils 0.1.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/components/index.cjs +2 -0
- package/dist/components/index.d.cts +73 -0
- package/dist/components/index.d.ts +73 -0
- package/dist/components/index.js +2 -0
- package/dist/hooks/index.cjs +2 -0
- package/dist/hooks/index.d.cts +101 -0
- package/dist/hooks/index.d.ts +101 -0
- package/dist/hooks/index.js +2 -0
- package/dist/index-BrziGArs.d.cts +163 -0
- package/dist/index-BrziGArs.d.ts +163 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +36 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +2 -0
- package/dist/server/index.cjs +1 -0
- package/dist/server/index.d.cts +213 -0
- package/dist/server/index.d.ts +213 -0
- package/dist/server/index.js +1 -0
- package/dist/types/index.cjs +1 -0
- package/dist/types/index.d.cts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +0 -0
- package/dist/utils/index.cjs +2 -0
- package/dist/utils/index.d.cts +36 -0
- package/dist/utils/index.d.ts +36 -0
- package/dist/utils/index.js +2 -0
- package/package.json +84 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { S as Session, g as ServerConfig } from '../index-BrziGArs.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Server-only session type that includes the JWT.
|
|
5
|
+
* NEVER export this from the client bundle.
|
|
6
|
+
*/
|
|
7
|
+
interface ServerSession extends Session {
|
|
8
|
+
jwt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Rate limiter interface — implement with Redis, Upstash, or use the in-memory default */
|
|
12
|
+
interface RateLimiter {
|
|
13
|
+
check(key: string): Promise<{
|
|
14
|
+
allowed: boolean;
|
|
15
|
+
remaining: number;
|
|
16
|
+
retryAfter?: number;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
/** Replay store interface — for preventing JWT re-sign replay attacks */
|
|
20
|
+
interface ReplayStore {
|
|
21
|
+
has(key: string): Promise<boolean>;
|
|
22
|
+
set(key: string, ttlSeconds: number): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* In-memory rate limiter with sliding window.
|
|
26
|
+
*
|
|
27
|
+
* Safe default for single-process deployments. For multi-process or
|
|
28
|
+
* distributed environments, provide a Redis-backed RateLimiter instead.
|
|
29
|
+
*/
|
|
30
|
+
declare function createInMemoryRateLimiter(opts: {
|
|
31
|
+
maxRequests: number;
|
|
32
|
+
windowMs: number;
|
|
33
|
+
}): RateLimiter;
|
|
34
|
+
/** In-memory replay store (single-process, entries auto-expire) */
|
|
35
|
+
declare function createInMemoryReplayStore(): ReplayStore;
|
|
36
|
+
|
|
37
|
+
interface AuthCallbackOptions {
|
|
38
|
+
rateLimiter?: RateLimiter;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Factory: POST handler for auth callback (login completion).
|
|
42
|
+
*
|
|
43
|
+
* Accepts wallet signature verification or OTP results from client,
|
|
44
|
+
* validates with StackNet, sets HttpOnly JWT cookie + public session cookie + CSRF cookie.
|
|
45
|
+
*/
|
|
46
|
+
declare function createAuthCallback(config: ServerConfig, opts?: AuthCallbackOptions): (request: Request) => Promise<Response>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Factory: POST handler for logout.
|
|
50
|
+
* Revokes session with StackNet and clears all auth cookies.
|
|
51
|
+
*/
|
|
52
|
+
declare function createLogoutHandler(config: Pick<ServerConfig, 'stacknetUrl' | 'secureCookies' | 'cookieDomain'>): (request: Request) => Promise<Response>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Factory: GET handler for session validation.
|
|
56
|
+
* Reads HttpOnly JWT cookie, validates, returns public session info.
|
|
57
|
+
* Transparently refreshes JWT if close to expiry.
|
|
58
|
+
*/
|
|
59
|
+
declare function createSessionHandler(config: Pick<ServerConfig, 'authSecret' | 'jwtExpiry' | 'secureCookies' | 'cookieDomain' | 'sessionMaxAge'>): (request: Request) => Promise<Response>;
|
|
60
|
+
|
|
61
|
+
interface OTPHandlerConfig extends Pick<ServerConfig, 'authSecret' | 'secureCookies' | 'cookieDomain' | 'sessionMaxAge' | 'jwtExpiry'> {
|
|
62
|
+
/** The OTP secret to validate against */
|
|
63
|
+
otpSecret: string;
|
|
64
|
+
/** Rate limiter (default: 5 attempts per 5 min per IP) */
|
|
65
|
+
rateLimiter?: RateLimiter;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Factory: POST handler for OTP verification.
|
|
69
|
+
* Validates OTP code, creates session JWT, sets HttpOnly cookie.
|
|
70
|
+
*/
|
|
71
|
+
declare function createOTPHandler(config: OTPHandlerConfig): (request: Request) => Promise<Response>;
|
|
72
|
+
|
|
73
|
+
interface BillingProxyConfig extends Pick<ServerConfig, 'authSecret' | 'stacknetUrl' | 'stackId' | 'stacknetJwtSecret' | 'secureCookies' | 'cookieDomain' | 'sessionMaxAge' | 'jwtExpiry'> {
|
|
74
|
+
/** Rate limiter for mutations (default: 20/min per user) */
|
|
75
|
+
rateLimiter?: RateLimiter;
|
|
76
|
+
}
|
|
77
|
+
type Handler = (request: Request) => Promise<Response>;
|
|
78
|
+
/**
|
|
79
|
+
* Factory: creates all billing route handlers that proxy to StackNet.
|
|
80
|
+
*
|
|
81
|
+
* All POST handlers validate CSRF tokens. All handlers validate the JWT cookie
|
|
82
|
+
* and transparently refresh if close to expiry.
|
|
83
|
+
*/
|
|
84
|
+
declare function createBillingProxy(config: BillingProxyConfig): {
|
|
85
|
+
plans: {
|
|
86
|
+
GET: Handler;
|
|
87
|
+
};
|
|
88
|
+
subscription: {
|
|
89
|
+
GET: Handler;
|
|
90
|
+
};
|
|
91
|
+
subscribe: {
|
|
92
|
+
POST: Handler;
|
|
93
|
+
};
|
|
94
|
+
cancel: {
|
|
95
|
+
POST: Handler;
|
|
96
|
+
};
|
|
97
|
+
usage: {
|
|
98
|
+
GET: Handler;
|
|
99
|
+
};
|
|
100
|
+
history: {
|
|
101
|
+
GET: Handler;
|
|
102
|
+
};
|
|
103
|
+
prepaid: {
|
|
104
|
+
POST: Handler;
|
|
105
|
+
};
|
|
106
|
+
verifyPrepaid: {
|
|
107
|
+
POST: Handler;
|
|
108
|
+
};
|
|
109
|
+
verifySession: {
|
|
110
|
+
POST: Handler;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Factory: POST handler for Stripe webhooks.
|
|
116
|
+
* Forwards raw body + stripe-signature to StackNet for verification and processing.
|
|
117
|
+
*/
|
|
118
|
+
declare function createWebhookHandler(config: Pick<ServerConfig, 'stacknetUrl' | 'stackId'>): (request: Request) => Promise<Response>;
|
|
119
|
+
|
|
120
|
+
interface CSRFConfig {
|
|
121
|
+
/** Cookie name (default: '__csrf') */
|
|
122
|
+
cookieName?: string;
|
|
123
|
+
/** Header name (default: 'x-csrf-token') */
|
|
124
|
+
headerName?: string;
|
|
125
|
+
/** Token length in bytes (default: 32) */
|
|
126
|
+
tokenLength?: number;
|
|
127
|
+
/** Use Secure flag on cookie (default: true) */
|
|
128
|
+
secure?: boolean;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Create CSRF protection using the double-submit cookie pattern.
|
|
132
|
+
*
|
|
133
|
+
* 1. Server sets a non-HttpOnly cookie with a random token
|
|
134
|
+
* 2. Client reads the cookie and sends the token in a header on mutations
|
|
135
|
+
* 3. Server validates cookie === header (attacker can't read cookie cross-origin)
|
|
136
|
+
*/
|
|
137
|
+
declare function createCSRFProtection(config?: CSRFConfig): {
|
|
138
|
+
/**
|
|
139
|
+
* Generate a CSRF token and add Set-Cookie header to a response.
|
|
140
|
+
* Call this on auth callback (login) to establish the CSRF cookie.
|
|
141
|
+
*/
|
|
142
|
+
generateToken(headers: Headers): string;
|
|
143
|
+
/**
|
|
144
|
+
* Validate a request's CSRF token (cookie vs header).
|
|
145
|
+
* Returns true if valid, false if not.
|
|
146
|
+
*/
|
|
147
|
+
validateRequest(request: Request): {
|
|
148
|
+
valid: boolean;
|
|
149
|
+
error?: string;
|
|
150
|
+
};
|
|
151
|
+
/** Cookie name for client-side reading */
|
|
152
|
+
cookieName: string;
|
|
153
|
+
/** Header name for client-side sending */
|
|
154
|
+
headerName: string;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/** Standard security response headers */
|
|
158
|
+
declare function securityHeaders(): Record<string, string>;
|
|
159
|
+
/**
|
|
160
|
+
* Wrap a request handler to add security headers to the response.
|
|
161
|
+
*/
|
|
162
|
+
declare function withSecurityHeaders(handler: (request: Request) => Promise<Response> | Response): (request: Request) => Promise<Response>;
|
|
163
|
+
/**
|
|
164
|
+
* Generate security headers config for Next.js next.config.ts.
|
|
165
|
+
*
|
|
166
|
+
* Usage in next.config.ts:
|
|
167
|
+
* ```ts
|
|
168
|
+
* import { nextSecurityHeaders } from '@stacknet/userutils/server';
|
|
169
|
+
* export default { headers: () => [{ source: '/(.*)', headers: nextSecurityHeaders() }] };
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
declare function nextSecurityHeaders(): Array<{
|
|
173
|
+
key: string;
|
|
174
|
+
value: string;
|
|
175
|
+
}>;
|
|
176
|
+
|
|
177
|
+
/** Decode JWT payload without verification (server-side helper) */
|
|
178
|
+
declare function decodeJWTPayload(jwt: string): Record<string, any> | null;
|
|
179
|
+
/** Sign a JWT with HMAC-SHA256 */
|
|
180
|
+
declare function signJWT(payload: Record<string, any>, secret: string): string;
|
|
181
|
+
/** Verify a JWT signature with HMAC-SHA256 (constant-time comparison) */
|
|
182
|
+
declare function verifyJWTSignature(jwt: string, secret: string): boolean;
|
|
183
|
+
/** Verify JWT and return payload if valid (checks signature + expiry) */
|
|
184
|
+
declare function verifyJWT(jwt: string, secret: string): Record<string, any> | null;
|
|
185
|
+
/**
|
|
186
|
+
* Check if JWT needs refresh and return a new one if so.
|
|
187
|
+
* Returns null if no refresh needed or JWT is invalid.
|
|
188
|
+
*/
|
|
189
|
+
declare function maybeRefreshJWT(jwt: string, secret: string, expirySeconds?: number, refreshWindowSeconds?: number): string | null;
|
|
190
|
+
/** Generate a cryptographically secure random token */
|
|
191
|
+
declare function generateToken(bytes?: number): string;
|
|
192
|
+
/** Extract IP address from request headers */
|
|
193
|
+
declare function extractIP(request: Request): string;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Re-sign a JWT using StackNet's HMAC-SHA256 scheme.
|
|
197
|
+
*
|
|
198
|
+
* Geoff apps sign JWTs with AUTH_SECRET. StackNet validates with JWT_SECRET.
|
|
199
|
+
* This function re-signs using the StackNet secret so the backend resolves
|
|
200
|
+
* the correct per-user identity.
|
|
201
|
+
*/
|
|
202
|
+
declare function resignForStackNet(jwt: string, stacknetJwtSecret: string): string | null;
|
|
203
|
+
/**
|
|
204
|
+
* Build headers for proxying a user-scoped request to StackNet.
|
|
205
|
+
* Re-signs the JWT so StackNet recognises the user's identity.
|
|
206
|
+
*/
|
|
207
|
+
declare function buildStackNetHeaders(jwt: string, stacknetJwtSecret: string): Record<string, string>;
|
|
208
|
+
/**
|
|
209
|
+
* Extract JWT from a request's cookies or Authorization header.
|
|
210
|
+
*/
|
|
211
|
+
declare function extractJwt(request: Request): string | null;
|
|
212
|
+
|
|
213
|
+
export { type AuthCallbackOptions, type BillingProxyConfig, type CSRFConfig, type OTPHandlerConfig, type RateLimiter, type ReplayStore, ServerConfig, type ServerSession, buildStackNetHeaders, createAuthCallback, createBillingProxy, createCSRFProtection, createInMemoryRateLimiter, createInMemoryReplayStore, createLogoutHandler, createOTPHandler, createSessionHandler, createWebhookHandler, decodeJWTPayload, extractIP, extractJwt, generateToken, maybeRefreshJWT, nextSecurityHeaders, resignForStackNet, securityHeaders, signJWT, verifyJWT, verifyJWTSignature, withSecurityHeaders };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { S as Session, g as ServerConfig } from '../index-BrziGArs.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Server-only session type that includes the JWT.
|
|
5
|
+
* NEVER export this from the client bundle.
|
|
6
|
+
*/
|
|
7
|
+
interface ServerSession extends Session {
|
|
8
|
+
jwt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Rate limiter interface — implement with Redis, Upstash, or use the in-memory default */
|
|
12
|
+
interface RateLimiter {
|
|
13
|
+
check(key: string): Promise<{
|
|
14
|
+
allowed: boolean;
|
|
15
|
+
remaining: number;
|
|
16
|
+
retryAfter?: number;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
/** Replay store interface — for preventing JWT re-sign replay attacks */
|
|
20
|
+
interface ReplayStore {
|
|
21
|
+
has(key: string): Promise<boolean>;
|
|
22
|
+
set(key: string, ttlSeconds: number): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* In-memory rate limiter with sliding window.
|
|
26
|
+
*
|
|
27
|
+
* Safe default for single-process deployments. For multi-process or
|
|
28
|
+
* distributed environments, provide a Redis-backed RateLimiter instead.
|
|
29
|
+
*/
|
|
30
|
+
declare function createInMemoryRateLimiter(opts: {
|
|
31
|
+
maxRequests: number;
|
|
32
|
+
windowMs: number;
|
|
33
|
+
}): RateLimiter;
|
|
34
|
+
/** In-memory replay store (single-process, entries auto-expire) */
|
|
35
|
+
declare function createInMemoryReplayStore(): ReplayStore;
|
|
36
|
+
|
|
37
|
+
interface AuthCallbackOptions {
|
|
38
|
+
rateLimiter?: RateLimiter;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Factory: POST handler for auth callback (login completion).
|
|
42
|
+
*
|
|
43
|
+
* Accepts wallet signature verification or OTP results from client,
|
|
44
|
+
* validates with StackNet, sets HttpOnly JWT cookie + public session cookie + CSRF cookie.
|
|
45
|
+
*/
|
|
46
|
+
declare function createAuthCallback(config: ServerConfig, opts?: AuthCallbackOptions): (request: Request) => Promise<Response>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Factory: POST handler for logout.
|
|
50
|
+
* Revokes session with StackNet and clears all auth cookies.
|
|
51
|
+
*/
|
|
52
|
+
declare function createLogoutHandler(config: Pick<ServerConfig, 'stacknetUrl' | 'secureCookies' | 'cookieDomain'>): (request: Request) => Promise<Response>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Factory: GET handler for session validation.
|
|
56
|
+
* Reads HttpOnly JWT cookie, validates, returns public session info.
|
|
57
|
+
* Transparently refreshes JWT if close to expiry.
|
|
58
|
+
*/
|
|
59
|
+
declare function createSessionHandler(config: Pick<ServerConfig, 'authSecret' | 'jwtExpiry' | 'secureCookies' | 'cookieDomain' | 'sessionMaxAge'>): (request: Request) => Promise<Response>;
|
|
60
|
+
|
|
61
|
+
interface OTPHandlerConfig extends Pick<ServerConfig, 'authSecret' | 'secureCookies' | 'cookieDomain' | 'sessionMaxAge' | 'jwtExpiry'> {
|
|
62
|
+
/** The OTP secret to validate against */
|
|
63
|
+
otpSecret: string;
|
|
64
|
+
/** Rate limiter (default: 5 attempts per 5 min per IP) */
|
|
65
|
+
rateLimiter?: RateLimiter;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Factory: POST handler for OTP verification.
|
|
69
|
+
* Validates OTP code, creates session JWT, sets HttpOnly cookie.
|
|
70
|
+
*/
|
|
71
|
+
declare function createOTPHandler(config: OTPHandlerConfig): (request: Request) => Promise<Response>;
|
|
72
|
+
|
|
73
|
+
interface BillingProxyConfig extends Pick<ServerConfig, 'authSecret' | 'stacknetUrl' | 'stackId' | 'stacknetJwtSecret' | 'secureCookies' | 'cookieDomain' | 'sessionMaxAge' | 'jwtExpiry'> {
|
|
74
|
+
/** Rate limiter for mutations (default: 20/min per user) */
|
|
75
|
+
rateLimiter?: RateLimiter;
|
|
76
|
+
}
|
|
77
|
+
type Handler = (request: Request) => Promise<Response>;
|
|
78
|
+
/**
|
|
79
|
+
* Factory: creates all billing route handlers that proxy to StackNet.
|
|
80
|
+
*
|
|
81
|
+
* All POST handlers validate CSRF tokens. All handlers validate the JWT cookie
|
|
82
|
+
* and transparently refresh if close to expiry.
|
|
83
|
+
*/
|
|
84
|
+
declare function createBillingProxy(config: BillingProxyConfig): {
|
|
85
|
+
plans: {
|
|
86
|
+
GET: Handler;
|
|
87
|
+
};
|
|
88
|
+
subscription: {
|
|
89
|
+
GET: Handler;
|
|
90
|
+
};
|
|
91
|
+
subscribe: {
|
|
92
|
+
POST: Handler;
|
|
93
|
+
};
|
|
94
|
+
cancel: {
|
|
95
|
+
POST: Handler;
|
|
96
|
+
};
|
|
97
|
+
usage: {
|
|
98
|
+
GET: Handler;
|
|
99
|
+
};
|
|
100
|
+
history: {
|
|
101
|
+
GET: Handler;
|
|
102
|
+
};
|
|
103
|
+
prepaid: {
|
|
104
|
+
POST: Handler;
|
|
105
|
+
};
|
|
106
|
+
verifyPrepaid: {
|
|
107
|
+
POST: Handler;
|
|
108
|
+
};
|
|
109
|
+
verifySession: {
|
|
110
|
+
POST: Handler;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Factory: POST handler for Stripe webhooks.
|
|
116
|
+
* Forwards raw body + stripe-signature to StackNet for verification and processing.
|
|
117
|
+
*/
|
|
118
|
+
declare function createWebhookHandler(config: Pick<ServerConfig, 'stacknetUrl' | 'stackId'>): (request: Request) => Promise<Response>;
|
|
119
|
+
|
|
120
|
+
interface CSRFConfig {
|
|
121
|
+
/** Cookie name (default: '__csrf') */
|
|
122
|
+
cookieName?: string;
|
|
123
|
+
/** Header name (default: 'x-csrf-token') */
|
|
124
|
+
headerName?: string;
|
|
125
|
+
/** Token length in bytes (default: 32) */
|
|
126
|
+
tokenLength?: number;
|
|
127
|
+
/** Use Secure flag on cookie (default: true) */
|
|
128
|
+
secure?: boolean;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Create CSRF protection using the double-submit cookie pattern.
|
|
132
|
+
*
|
|
133
|
+
* 1. Server sets a non-HttpOnly cookie with a random token
|
|
134
|
+
* 2. Client reads the cookie and sends the token in a header on mutations
|
|
135
|
+
* 3. Server validates cookie === header (attacker can't read cookie cross-origin)
|
|
136
|
+
*/
|
|
137
|
+
declare function createCSRFProtection(config?: CSRFConfig): {
|
|
138
|
+
/**
|
|
139
|
+
* Generate a CSRF token and add Set-Cookie header to a response.
|
|
140
|
+
* Call this on auth callback (login) to establish the CSRF cookie.
|
|
141
|
+
*/
|
|
142
|
+
generateToken(headers: Headers): string;
|
|
143
|
+
/**
|
|
144
|
+
* Validate a request's CSRF token (cookie vs header).
|
|
145
|
+
* Returns true if valid, false if not.
|
|
146
|
+
*/
|
|
147
|
+
validateRequest(request: Request): {
|
|
148
|
+
valid: boolean;
|
|
149
|
+
error?: string;
|
|
150
|
+
};
|
|
151
|
+
/** Cookie name for client-side reading */
|
|
152
|
+
cookieName: string;
|
|
153
|
+
/** Header name for client-side sending */
|
|
154
|
+
headerName: string;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/** Standard security response headers */
|
|
158
|
+
declare function securityHeaders(): Record<string, string>;
|
|
159
|
+
/**
|
|
160
|
+
* Wrap a request handler to add security headers to the response.
|
|
161
|
+
*/
|
|
162
|
+
declare function withSecurityHeaders(handler: (request: Request) => Promise<Response> | Response): (request: Request) => Promise<Response>;
|
|
163
|
+
/**
|
|
164
|
+
* Generate security headers config for Next.js next.config.ts.
|
|
165
|
+
*
|
|
166
|
+
* Usage in next.config.ts:
|
|
167
|
+
* ```ts
|
|
168
|
+
* import { nextSecurityHeaders } from '@stacknet/userutils/server';
|
|
169
|
+
* export default { headers: () => [{ source: '/(.*)', headers: nextSecurityHeaders() }] };
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
declare function nextSecurityHeaders(): Array<{
|
|
173
|
+
key: string;
|
|
174
|
+
value: string;
|
|
175
|
+
}>;
|
|
176
|
+
|
|
177
|
+
/** Decode JWT payload without verification (server-side helper) */
|
|
178
|
+
declare function decodeJWTPayload(jwt: string): Record<string, any> | null;
|
|
179
|
+
/** Sign a JWT with HMAC-SHA256 */
|
|
180
|
+
declare function signJWT(payload: Record<string, any>, secret: string): string;
|
|
181
|
+
/** Verify a JWT signature with HMAC-SHA256 (constant-time comparison) */
|
|
182
|
+
declare function verifyJWTSignature(jwt: string, secret: string): boolean;
|
|
183
|
+
/** Verify JWT and return payload if valid (checks signature + expiry) */
|
|
184
|
+
declare function verifyJWT(jwt: string, secret: string): Record<string, any> | null;
|
|
185
|
+
/**
|
|
186
|
+
* Check if JWT needs refresh and return a new one if so.
|
|
187
|
+
* Returns null if no refresh needed or JWT is invalid.
|
|
188
|
+
*/
|
|
189
|
+
declare function maybeRefreshJWT(jwt: string, secret: string, expirySeconds?: number, refreshWindowSeconds?: number): string | null;
|
|
190
|
+
/** Generate a cryptographically secure random token */
|
|
191
|
+
declare function generateToken(bytes?: number): string;
|
|
192
|
+
/** Extract IP address from request headers */
|
|
193
|
+
declare function extractIP(request: Request): string;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Re-sign a JWT using StackNet's HMAC-SHA256 scheme.
|
|
197
|
+
*
|
|
198
|
+
* Geoff apps sign JWTs with AUTH_SECRET. StackNet validates with JWT_SECRET.
|
|
199
|
+
* This function re-signs using the StackNet secret so the backend resolves
|
|
200
|
+
* the correct per-user identity.
|
|
201
|
+
*/
|
|
202
|
+
declare function resignForStackNet(jwt: string, stacknetJwtSecret: string): string | null;
|
|
203
|
+
/**
|
|
204
|
+
* Build headers for proxying a user-scoped request to StackNet.
|
|
205
|
+
* Re-signs the JWT so StackNet recognises the user's identity.
|
|
206
|
+
*/
|
|
207
|
+
declare function buildStackNetHeaders(jwt: string, stacknetJwtSecret: string): Record<string, string>;
|
|
208
|
+
/**
|
|
209
|
+
* Extract JWT from a request's cookies or Authorization header.
|
|
210
|
+
*/
|
|
211
|
+
declare function extractJwt(request: Request): string | null;
|
|
212
|
+
|
|
213
|
+
export { type AuthCallbackOptions, type BillingProxyConfig, type CSRFConfig, type OTPHandlerConfig, type RateLimiter, type ReplayStore, ServerConfig, type ServerSession, buildStackNetHeaders, createAuthCallback, createBillingProxy, createCSRFProtection, createInMemoryRateLimiter, createInMemoryReplayStore, createLogoutHandler, createOTPHandler, createSessionHandler, createWebhookHandler, decodeJWTPayload, extractIP, extractJwt, generateToken, maybeRefreshJWT, nextSecurityHeaders, resignForStackNet, securityHeaders, signJWT, verifyJWT, verifyJWTSignature, withSecurityHeaders };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {createHmac,timingSafeEqual,randomBytes,createHash}from'crypto';function N(e){return Buffer.from(e).toString("base64url")}function z(e){return Buffer.from(e,"base64url").toString()}function b(e){try{let r=e.split(".");return r.length!==3?null:JSON.parse(z(r[1]))}catch{return null}}function P(e,r){let t=N(JSON.stringify({alg:"HS256",typ:"JWT"})),n=N(JSON.stringify(e)),s=createHmac("sha256",r).update(`${t}.${n}`).digest("base64url");return `${t}.${n}.${s}`}function W(e,r){try{let t=e.split(".");if(t.length!==3)return !1;let[n,s,o]=t,a=createHmac("sha256",r).update(`${n}.${s}`).digest("base64url"),f=Buffer.from(o),p=Buffer.from(a);return f.length!==p.length?!1:timingSafeEqual(f,p)}catch{return false}}function C(e,r){if(!W(e,r))return null;let t=b(e);return !t||t.exp&&t.exp<Math.floor(Date.now()/1e3)?null:t}function $(e,r,t=900,n=300){let s=C(e,r);return !s?.exp||s.exp*1e3-Date.now()>n*1e3?null:P({...s,exp:Math.floor(Date.now()/1e3)+t},r)}function D(e=32){return randomBytes(e).toString("hex")}function j(e){return e.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||e.headers.get("x-real-ip")||"unknown"}var Y="__csrf",Q="x-csrf-token";function x(e={}){let r=e.cookieName||Y,t=e.headerName||Q,n=e.tokenLength||32,s=e.secure!==false;return {generateToken(o){let a=D(n),f=[`${r}=${a}`,"Path=/","SameSite=Lax"];return s&&f.push("Secure"),o.append("Set-Cookie",f.join("; ")),a},validateRequest(o){let a=o.headers.get("cookie");if(!a)return {valid:false,error:"No cookies present"};let f=a.split(";").map(l=>l.trim()).find(l=>l.startsWith(`${r}=`))?.slice(r.length+1);if(!f)return {valid:false,error:"CSRF cookie missing"};let p=o.headers.get(t);if(!p)return {valid:false,error:"CSRF header missing"};try{let l=Buffer.from(f),u=Buffer.from(p);return l.length!==u.length?{valid:!1,error:"CSRF token mismatch"}:timingSafeEqual(l,u)?{valid:!0}:{valid:!1,error:"CSRF token mismatch"}}catch{return {valid:false,error:"CSRF validation failed"}}},cookieName:r,headerName:t}}function w(e){let r=new Map,t=setInterval(()=>{let n=Date.now();for(let[s,o]of r)n>=o.resetAt&&r.delete(s);},6e4);return typeof t=="object"&&"unref"in t&&t.unref(),{async check(n){let s=Date.now(),o=r.get(n);return (!o||s>=o.resetAt)&&(o={count:0,resetAt:s+e.windowMs},r.set(n,o)),o.count++,o.count>e.maxRequests?{allowed:false,remaining:0,retryAfter:Math.ceil((o.resetAt-s)/1e3)}:{allowed:true,remaining:e.maxRequests-o.count}}}}function Z(){let e=new Map,r=setInterval(()=>{let t=Date.now();for(let[n,s]of e)t>=s&&e.delete(n);},6e4);return typeof r=="object"&&"unref"in r&&r.unref(),{async has(t){let n=e.get(t);return n?Date.now()>=n?(e.delete(t),false):true:false},async set(t,n){e.set(t,Date.now()+n*1e3);}}}function ee(e,r){let t=r?.rateLimiter||w({maxRequests:10,windowMs:6e4}),n=x({secure:e.secureCookies!==false}),s=e.jwtExpiry||900,o=e.sessionMaxAge||604800;e.stacknetJwtSecret||e.authSecret;return async function(p){let l=j(p),u=await t.check(`auth:${l}`);if(!u.allowed)return Response.json({error:"Too many login attempts. Please wait."},{status:429,headers:{"Retry-After":String(u.retryAfter||60)}});let i;try{i=await p.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let{chain:c,message:d,signature:m,publicKey:y,otp:g,code:k,redirectUrl:T}=i,h;if(c&&d&&m){let G={"Content-Type":"application/json"},M=await fetch(`${e.stacknetUrl}/api/v2/stacks/${e.stackId}/auth/web3/verify`,{method:"POST",headers:G,body:JSON.stringify({chain:c,message:d,signature:m,public_key:y}),signal:AbortSignal.timeout(1e4)});if(!M.ok){await M.json().catch(()=>({}));return Response.json({error:"Wallet verification failed"},{status:401})}let O=await M.json();h=O.data?.session||O.session||O.data||O;}else return g||k?Response.json({error:"Use /api/auth/otp for OTP verification"},{status:400}):Response.json({error:"Provide wallet signature or OTP code"},{status:400});if(!h?.jwt)return Response.json({error:"Authentication failed \u2014 no session returned"},{status:401});let S=JSON.parse(Buffer.from(h.jwt.split(".")[1],"base64url").toString()),H=Math.floor(Date.now()/1e3),v={...S,exp:H+s,iat:H},U=P(v,e.authSecret),L={userId:S.sub||S.user_id||S.session_id||S.global_id||"",address:h.address||S.address,chain:h.chain||c,expiresAt:Date.now()+o*1e3,authMethod:c?`web3:${c}`:"otp"},A=new Headers({"Content-Type":"application/json"}),I=e.secureCookies!==false?"; Secure":"",E=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";A.append("Set-Cookie",`stackauth_jwt=${U}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${o}${I}${E}`);let F=Buffer.from(JSON.stringify(L)).toString("base64url");return A.append("Set-Cookie",`stackauth_session=${F}; Path=/; SameSite=Lax; Max-Age=${o}${I}${E}`),n.generateToken(A),new Response(JSON.stringify({user:L}),{status:200,headers:A})}}function B(e,r){if(!r)return null;try{let t=b(e);if(!t||t.exp&&t.exp<Math.floor(Date.now()/1e3))return null;let n=Buffer.from(JSON.stringify({alg:"HS256",typ:"JWT"})).toString("base64url"),s=Buffer.from(JSON.stringify(t)).toString("base64url"),o=createHmac("sha256",r).update(`${n}.${s}`).digest("base64url");return `${n}.${s}.${o}`}catch{return null}}function J(e,r){let t=B(e,r);return t?{Cookie:`stackauth_jwt=${t}`}:{Cookie:`stackauth_jwt=${e}`}}function R(e){let r=e.headers.get("cookie");if(r){let n=r.split(";").map(s=>s.trim()).find(s=>s.startsWith("stackauth_jwt="));if(n)return n.slice(14)}let t=e.headers.get("authorization");return t?.startsWith("Bearer ")?t.slice(7):null}function re(e){return async function(t){let n=R(t);if(n){let f=b(n),p=f?.session_id||f?.sub;if(p)try{await fetch(`${e.stacknetUrl}/api/v2/sessions/${p}`,{method:"DELETE",signal:AbortSignal.timeout(5e3)});}catch{}}let s=e.secureCookies!==false?"; Secure":"",o=e.cookieDomain?`; Domain=${e.cookieDomain}`:"",a=new Headers({"Content-Type":"application/json"});return a.append("Set-Cookie",`stackauth_jwt=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${s}${o}`),a.append("Set-Cookie",`stackauth_session=; Path=/; SameSite=Lax; Max-Age=0${s}${o}`),a.append("Set-Cookie",`__csrf=; Path=/; SameSite=Lax; Max-Age=0${s}${o}`),new Response(JSON.stringify({success:true}),{status:200,headers:a})}}function se(e){let r=e.jwtExpiry||900,t=e.sessionMaxAge||604800;return async function(s){let o=R(s);if(!o)return Response.json({session:null},{status:200});let a=C(o,e.authSecret);if(!a)return Response.json({session:null},{status:200});let p={userId:a.sub||a.user_id||a.session_id||a.global_id||"",address:a.address,chain:a.chain,expiresAt:a.session_expires_at||(a.exp?a.exp*1e3:Date.now()+t*1e3),planId:a.plan_id,authMethod:a.auth_method},l=new Headers({"Content-Type":"application/json"}),u=$(o,e.authSecret,r,300);if(u){let i=e.secureCookies!==false?"; Secure":"",c=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";l.append("Set-Cookie",`stackauth_jwt=${u}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${t}${i}${c}`);}return new Response(JSON.stringify({session:p}),{status:200,headers:l})}}function ae(e,r){if(e.length!==r.length)return false;try{return timingSafeEqual(Buffer.from(e),Buffer.from(r))}catch{return false}}function ie(e){let r=e.rateLimiter||w({maxRequests:5,windowMs:3e5}),t=x({secure:e.secureCookies!==false}),n=e.jwtExpiry||900,s=e.sessionMaxAge||604800;return async function(a){let f=j(a),p=await r.check(`otp:${f}`);if(!p.allowed)return Response.json({error:"Too many attempts. Please wait."},{status:429,headers:{"Retry-After":String(p.retryAfter||300)}});let l;try{l=await a.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let{code:u}=l;if(!u||typeof u!="string"||u.length!==6)return Response.json({error:"Invalid code format"},{status:400});if(!ae(u,e.otpSecret))return Response.json({error:"Invalid code"},{status:401});let i=Math.floor(Date.now()/1e3),d={sub:`otp:${createHash("sha256").update(`otp:${u}:${Date.now()}`).digest("hex").slice(0,32)}`,auth_method:"otp",iat:i,exp:i+n},m=P(d,e.authSecret),y={userId:d.sub,expiresAt:Date.now()+s*1e3,authMethod:"otp"},g=new Headers({"Content-Type":"application/json"}),k=e.secureCookies!==false?"; Secure":"",T=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";g.append("Set-Cookie",`stackauth_jwt=${m}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${s}${k}${T}`);let h=Buffer.from(JSON.stringify(y)).toString("base64url");return g.append("Set-Cookie",`stackauth_session=${h}; Path=/; SameSite=Lax; Max-Age=${s}${k}${T}`),t.generateToken(g),new Response(JSON.stringify({success:true,data:{user:y}}),{status:200,headers:g})}}function ce(e){let r=x({secure:e.secureCookies!==false}),t=e.rateLimiter||w({maxRequests:20,windowMs:6e4}),n=e.stacknetJwtSecret||e.authSecret,s=e.jwtExpiry||900,o=e.sessionMaxAge||604800;function a(i){let c=R(i);if(!c)return null;let d=C(c,e.authSecret);return d?{jwt:c,payload:d}:null}function f(i,c){let d=$(i,e.authSecret,s,300);if(d){let m=e.secureCookies!==false?"; Secure":"",y=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";c.append("Set-Cookie",`stackauth_jwt=${d}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${o}${m}${y}`);}}async function p(i,c){let d=a(i);if(!d)return Response.json({error:"Unauthorized"},{status:401});let m=J(d.jwt,n),y=await fetch(`${e.stacknetUrl}${c}`,{headers:m,signal:AbortSignal.timeout(15e3)}),g=await y.json().catch(()=>({})),k=new Headers({"Content-Type":"application/json"});return f(d.jwt,k),new Response(JSON.stringify(g),{status:y.status,headers:k})}async function l(i,c,d){let m=a(i);if(!m)return Response.json({error:"Unauthorized"},{status:401});let y=r.validateRequest(i);if(!y.valid)return Response.json({error:y.error||"CSRF validation failed"},{status:403});let g=m.payload.sub||m.payload.user_id||"unknown";if(!(await t.check(`billing:${g}`)).allowed)return Response.json({error:"Too many requests"},{status:429});let T=await i.json().catch(()=>({})),h=J(m.jwt,n);h["Content-Type"]="application/json";let S=await fetch(`${e.stacknetUrl}${c}`,{method:"POST",headers:h,body:JSON.stringify({...T,...d}),signal:AbortSignal.timeout(15e3)}),H=await S.json().catch(()=>({})),v=new Headers({"Content-Type":"application/json"});return f(m.jwt,v),new Response(JSON.stringify(H),{status:S.status,headers:v})}let u=`/api/v2/stacks/${e.stackId}`;return {plans:{GET:async i=>{let c=await fetch(`${e.stacknetUrl}${u}/plans`,{signal:AbortSignal.timeout(1e4)}),d=await c.json().catch(()=>({}));return Response.json(d,{status:c.status})}},subscription:{GET:(i=>p(i,`${u}/subscription`))},subscribe:{POST:(i=>{let c=new URL(i.url).origin;return l(i,`${u}/subscribe`,{successUrl:`${c}/billing/success?session_id={CHECKOUT_SESSION_ID}`,cancelUrl:`${c}/pricing`})})},cancel:{POST:(i=>l(i,`${u}/cancel-subscription`))},usage:{GET:(i=>p(i,"/v1/account/usage"))},history:{GET:(i=>p(i,`${u}/billing`))},prepaid:{POST:(i=>{let c=new URL(i.url).origin;return l(i,`${u}/prepaid`,{successUrl:`${c}/pricing/prepaid/success?session_id={CHECKOUT_SESSION_ID}`,cancelUrl:`${c}/pricing/prepaid`})})},verifyPrepaid:{POST:(i=>l(i,`${u}/verify-prepaid`))},verifySession:{POST:(i=>l(i,`${u}/verify-session`))}}}function ue(e){return async function(t){let n=t.headers.get("stripe-signature");if(!n)return Response.json({error:"Missing Stripe signature"},{status:400});try{let s=await t.text(),o=await fetch(`${e.stacknetUrl}/api/v2/stacks/${e.stackId}/webhook/stripe`,{method:"POST",headers:{"Content-Type":"application/json","stripe-signature":n},body:s,signal:AbortSignal.timeout(1e4)}),a=await o.json().catch(()=>({received:!0}));return Response.json(a,{status:o.status})}catch{return Response.json({error:"Webhook processing failed"},{status:502})}}}function _(){return {"Strict-Transport-Security":"max-age=63072000; includeSubDomains; preload","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","X-XSS-Protection":"0","Referrer-Policy":"strict-origin-when-cross-origin","Permissions-Policy":"camera=(), microphone=(), geolocation=()"}}function pe(e){return async r=>{let t=await e(r),n=_(),s=new Headers(t.headers);for(let[o,a]of Object.entries(n))s.set(o,a);return new Response(t.body,{status:t.status,statusText:t.statusText,headers:s})}}function le(){return Object.entries(_()).map(([e,r])=>({key:e,value:r}))}export{J as buildStackNetHeaders,ee as createAuthCallback,ce as createBillingProxy,x as createCSRFProtection,w as createInMemoryRateLimiter,Z as createInMemoryReplayStore,re as createLogoutHandler,ie as createOTPHandler,se as createSessionHandler,ue as createWebhookHandler,b as decodeJWTPayload,j as extractIP,R as extractJwt,D as generateToken,$ as maybeRefreshJWT,le as nextSecurityHeaders,B as resignForStackNet,_ as securityHeaders,P as signJWT,C as verifyJWT,W as verifyJWTSignature,pe as withSecurityHeaders};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { A as APIResponse, B as BillingPlan, a as BillingRecord, M as MPCNode, N as NetworkStatus, P as PrepaidCheckoutResult, b as PrepaidVerifyResult, c as PublicSession, S as Session, d as Subscription, U as UsageSummary, e as UserUtilsCallbacks, f as UserUtilsConfig, W as Web3Chain } from '../index-BrziGArs.cjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { A as APIResponse, B as BillingPlan, a as BillingRecord, M as MPCNode, N as NetworkStatus, P as PrepaidCheckoutResult, b as PrepaidVerifyResult, c as PublicSession, S as Session, d as Subscription, U as UsageSummary, e as UserUtilsCallbacks, f as UserUtilsConfig, W as Web3Chain } from '../index-BrziGArs.js';
|
|
File without changes
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var clsx=require('clsx'),tailwindMerge=require('tailwind-merge');function i(...t){return tailwindMerge.twMerge(clsx.clsx(t))}function s(t){return t>=1e12?`${(t/1e12).toFixed(t%1e12===0?0:1)}T`:t>=1e9?`${(t/1e9).toFixed(t%1e9===0?0:1)}B`:t>=1e6?`${(t/1e6).toFixed(t%1e6===0?0:1)}M`:t>=1e3?`${(t/1e3).toFixed(0)}K`:t.toLocaleString()}function a(t,r){if(!t)return "/";if(t.startsWith("/")&&!t.startsWith("//"))return t;try{let e=new URL(t,r);return e.origin!==r?"/":e.pathname+e.search+e.hash}catch{return "/"}}function u(t){try{let r=t.split(".");if(r.length!==3)return null;let e=atob(r[1].replace(/-/g,"+").replace(/_/g,"/"));return JSON.parse(e)}catch{return null}}function c(){if(typeof document>"u")return null;try{let t=document.cookie.split(";").map(e=>e.trim()).find(e=>e.startsWith("stackauth_session="));if(!t)return null;let r=t.slice(18);return JSON.parse(atob(r.replace(/-/g,"+").replace(/_/g,"/")))}catch{return null}}function l(t="__csrf"){if(typeof document>"u")return null;let r=document.cookie.split(";").map(e=>e.trim()).find(e=>e.startsWith(`${t}=`));return r?r.slice(t.length+1):null}
|
|
2
|
+
exports.cn=i;exports.decodeJwtPayloadClient=u;exports.formatTokens=s;exports.readCSRFCookie=l;exports.readSessionCookie=c;exports.validateRedirectUrl=a;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ClassValue } from 'clsx';
|
|
2
|
+
|
|
3
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
4
|
+
|
|
5
|
+
declare function formatTokens(n: number): string;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate a redirect URL to prevent open redirect attacks.
|
|
9
|
+
* Returns the URL if it's a relative path or matches the allowed origin.
|
|
10
|
+
* Returns '/' for any external or invalid URL.
|
|
11
|
+
*/
|
|
12
|
+
declare function validateRedirectUrl(url: string, allowedOrigin: string): string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Client-side JWT payload decode — NO VERIFICATION.
|
|
16
|
+
* For display purposes only. Never trust the result for auth decisions.
|
|
17
|
+
*/
|
|
18
|
+
declare function decodeJwtPayloadClient(jwt: string): Record<string, unknown> | null;
|
|
19
|
+
/**
|
|
20
|
+
* Read the public session cookie (stackauth_session).
|
|
21
|
+
* Returns parsed session data or null.
|
|
22
|
+
*/
|
|
23
|
+
declare function readSessionCookie(): {
|
|
24
|
+
userId: string;
|
|
25
|
+
expiresAt: number;
|
|
26
|
+
planId?: string;
|
|
27
|
+
address?: string;
|
|
28
|
+
chain?: string;
|
|
29
|
+
authMethod?: string;
|
|
30
|
+
} | null;
|
|
31
|
+
/**
|
|
32
|
+
* Read the CSRF token cookie.
|
|
33
|
+
*/
|
|
34
|
+
declare function readCSRFCookie(cookieName?: string): string | null;
|
|
35
|
+
|
|
36
|
+
export { cn, decodeJwtPayloadClient, formatTokens, readCSRFCookie, readSessionCookie, validateRedirectUrl };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ClassValue } from 'clsx';
|
|
2
|
+
|
|
3
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
4
|
+
|
|
5
|
+
declare function formatTokens(n: number): string;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate a redirect URL to prevent open redirect attacks.
|
|
9
|
+
* Returns the URL if it's a relative path or matches the allowed origin.
|
|
10
|
+
* Returns '/' for any external or invalid URL.
|
|
11
|
+
*/
|
|
12
|
+
declare function validateRedirectUrl(url: string, allowedOrigin: string): string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Client-side JWT payload decode — NO VERIFICATION.
|
|
16
|
+
* For display purposes only. Never trust the result for auth decisions.
|
|
17
|
+
*/
|
|
18
|
+
declare function decodeJwtPayloadClient(jwt: string): Record<string, unknown> | null;
|
|
19
|
+
/**
|
|
20
|
+
* Read the public session cookie (stackauth_session).
|
|
21
|
+
* Returns parsed session data or null.
|
|
22
|
+
*/
|
|
23
|
+
declare function readSessionCookie(): {
|
|
24
|
+
userId: string;
|
|
25
|
+
expiresAt: number;
|
|
26
|
+
planId?: string;
|
|
27
|
+
address?: string;
|
|
28
|
+
chain?: string;
|
|
29
|
+
authMethod?: string;
|
|
30
|
+
} | null;
|
|
31
|
+
/**
|
|
32
|
+
* Read the CSRF token cookie.
|
|
33
|
+
*/
|
|
34
|
+
declare function readCSRFCookie(cookieName?: string): string | null;
|
|
35
|
+
|
|
36
|
+
export { cn, decodeJwtPayloadClient, formatTokens, readCSRFCookie, readSessionCookie, validateRedirectUrl };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {clsx}from'clsx';import {twMerge}from'tailwind-merge';function i(...t){return twMerge(clsx(t))}function s(t){return t>=1e12?`${(t/1e12).toFixed(t%1e12===0?0:1)}T`:t>=1e9?`${(t/1e9).toFixed(t%1e9===0?0:1)}B`:t>=1e6?`${(t/1e6).toFixed(t%1e6===0?0:1)}M`:t>=1e3?`${(t/1e3).toFixed(0)}K`:t.toLocaleString()}function a(t,r){if(!t)return "/";if(t.startsWith("/")&&!t.startsWith("//"))return t;try{let e=new URL(t,r);return e.origin!==r?"/":e.pathname+e.search+e.hash}catch{return "/"}}function u(t){try{let r=t.split(".");if(r.length!==3)return null;let e=atob(r[1].replace(/-/g,"+").replace(/_/g,"/"));return JSON.parse(e)}catch{return null}}function c(){if(typeof document>"u")return null;try{let t=document.cookie.split(";").map(e=>e.trim()).find(e=>e.startsWith("stackauth_session="));if(!t)return null;let r=t.slice(18);return JSON.parse(atob(r.replace(/-/g,"+").replace(/_/g,"/")))}catch{return null}}function l(t="__csrf"){if(typeof document>"u")return null;let r=document.cookie.split(";").map(e=>e.trim()).find(e=>e.startsWith(`${t}=`));return r?r.slice(t.length+1):null}
|
|
2
|
+
export{i as cn,u as decodeJwtPayloadClient,s as formatTokens,l as readCSRFCookie,c as readSessionCookie,a as validateRedirectUrl};
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stacknet/userutils",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reusable auth, billing, and security utilities for StackNet stacks and applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./components": {
|
|
16
|
+
"types": "./dist/components/index.d.ts",
|
|
17
|
+
"import": "./dist/components/index.js",
|
|
18
|
+
"require": "./dist/components/index.cjs"
|
|
19
|
+
},
|
|
20
|
+
"./hooks": {
|
|
21
|
+
"types": "./dist/hooks/index.d.ts",
|
|
22
|
+
"import": "./dist/hooks/index.js",
|
|
23
|
+
"require": "./dist/hooks/index.cjs"
|
|
24
|
+
},
|
|
25
|
+
"./types": {
|
|
26
|
+
"types": "./dist/types/index.d.ts",
|
|
27
|
+
"import": "./dist/types/index.js",
|
|
28
|
+
"require": "./dist/types/index.cjs"
|
|
29
|
+
},
|
|
30
|
+
"./utils": {
|
|
31
|
+
"types": "./dist/utils/index.d.ts",
|
|
32
|
+
"import": "./dist/utils/index.js",
|
|
33
|
+
"require": "./dist/utils/index.cjs"
|
|
34
|
+
},
|
|
35
|
+
"./server": {
|
|
36
|
+
"types": "./dist/server/index.d.ts",
|
|
37
|
+
"import": "./dist/server/index.js",
|
|
38
|
+
"require": "./dist/server/index.cjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"build:publish": "tsup --no-sourcemap --minify",
|
|
48
|
+
"dev": "tsup --watch",
|
|
49
|
+
"clean": "rm -rf dist",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"prepublishOnly": "pnpm run clean && pnpm run typecheck && pnpm run build:publish",
|
|
52
|
+
"release": "pnpm run prepublishOnly && npm publish --access public",
|
|
53
|
+
"release:dry": "pnpm run prepublishOnly && npm pack --dry-run"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"clsx": "^2.1.1",
|
|
57
|
+
"tailwind-merge": "^2.5.4",
|
|
58
|
+
"lucide-react": "^0.460.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/react": "^19.0.0",
|
|
62
|
+
"@types/react-dom": "^19.0.0",
|
|
63
|
+
"react": "^19.0.0",
|
|
64
|
+
"react-dom": "^19.0.0",
|
|
65
|
+
"tsup": "^8.3.5",
|
|
66
|
+
"typescript": "^5.6.3"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"react": ">=18.0.0",
|
|
70
|
+
"react-dom": ">=18.0.0"
|
|
71
|
+
},
|
|
72
|
+
"keywords": [
|
|
73
|
+
"react",
|
|
74
|
+
"stacknet",
|
|
75
|
+
"auth",
|
|
76
|
+
"billing",
|
|
77
|
+
"web3",
|
|
78
|
+
"security"
|
|
79
|
+
],
|
|
80
|
+
"license": "MIT",
|
|
81
|
+
"repository": {
|
|
82
|
+
"type": "git"
|
|
83
|
+
}
|
|
84
|
+
}
|