@reauth-dev/sdk 0.1.0 → 0.3.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/chunk-5LFJ5PXQ.mjs +1042 -0
- package/dist/chunk-EY5LQCDG.mjs +6 -0
- package/dist/index.d.mts +201 -16
- package/dist/index.d.ts +201 -16
- package/dist/index.js +801 -29
- package/dist/index.mjs +2 -1
- package/dist/react/index.d.mts +75 -5
- package/dist/react/index.d.ts +75 -5
- package/dist/react/index.js +965 -44
- package/dist/react/index.mjs +160 -11
- package/dist/server.d.mts +20 -2
- package/dist/server.d.ts +20 -2
- package/dist/server.js +61 -12
- package/dist/server.mjs +57 -11
- package/dist/types-DKUKhCNE.d.mts +349 -0
- package/dist/types-DKUKhCNE.d.ts +349 -0
- package/dist/webhooks.js +7 -3
- package/dist/webhooks.mjs +7 -3
- package/package.json +5 -2
- package/dist/chunk-JX2J36FS.mjs +0 -269
- package/dist/types-D8oOYbeC.d.mts +0 -169
- package/dist/types-D8oOYbeC.d.ts +0 -169
package/dist/react/index.mjs
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createReauthClient
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-5LFJ5PXQ.mjs";
|
|
4
|
+
import "../chunk-EY5LQCDG.mjs";
|
|
4
5
|
|
|
5
6
|
// src/react/useAuth.ts
|
|
6
|
-
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
7
|
+
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
8
|
+
var DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
7
9
|
function useAuth(config) {
|
|
8
|
-
const
|
|
10
|
+
const { refreshInterval = DEFAULT_REFRESH_INTERVAL_MS, ...clientConfig } = config;
|
|
11
|
+
const client = useMemo(() => createReauthClient(clientConfig), [clientConfig.domain]);
|
|
12
|
+
const isRefreshing = useRef(false);
|
|
9
13
|
const [state, setState] = useState({
|
|
10
14
|
user: null,
|
|
11
15
|
loading: true,
|
|
@@ -14,12 +18,15 @@ function useAuth(config) {
|
|
|
14
18
|
waitlistPosition: null
|
|
15
19
|
});
|
|
16
20
|
const checkSession = useCallback(async () => {
|
|
21
|
+
if (isRefreshing.current) return;
|
|
22
|
+
isRefreshing.current = true;
|
|
17
23
|
try {
|
|
18
24
|
let session = await client.getSession();
|
|
19
25
|
if (!session.valid && !session.error_code && !session.end_user_id) {
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
try {
|
|
27
|
+
await client.refresh();
|
|
22
28
|
session = await client.getSession();
|
|
29
|
+
} catch {
|
|
23
30
|
}
|
|
24
31
|
}
|
|
25
32
|
if (session.error_code === "ACCOUNT_SUSPENDED") {
|
|
@@ -37,7 +44,9 @@ function useAuth(config) {
|
|
|
37
44
|
user: {
|
|
38
45
|
id: session.end_user_id,
|
|
39
46
|
email: session.email,
|
|
40
|
-
roles: session.roles || []
|
|
47
|
+
roles: session.roles || [],
|
|
48
|
+
activeOrgId: session.active_org_id || "",
|
|
49
|
+
orgRole: session.org_role || ""
|
|
41
50
|
},
|
|
42
51
|
loading: false,
|
|
43
52
|
error: null,
|
|
@@ -51,7 +60,9 @@ function useAuth(config) {
|
|
|
51
60
|
user: {
|
|
52
61
|
id: session.end_user_id,
|
|
53
62
|
email: session.email,
|
|
54
|
-
roles: session.roles || []
|
|
63
|
+
roles: session.roles || [],
|
|
64
|
+
activeOrgId: session.active_org_id || "",
|
|
65
|
+
orgRole: session.org_role || ""
|
|
55
66
|
},
|
|
56
67
|
loading: false,
|
|
57
68
|
error: null,
|
|
@@ -75,11 +86,20 @@ function useAuth(config) {
|
|
|
75
86
|
isOnWaitlist: false,
|
|
76
87
|
waitlistPosition: null
|
|
77
88
|
});
|
|
89
|
+
} finally {
|
|
90
|
+
isRefreshing.current = false;
|
|
78
91
|
}
|
|
79
92
|
}, [client]);
|
|
80
93
|
useEffect(() => {
|
|
81
94
|
checkSession();
|
|
82
95
|
}, [checkSession]);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (refreshInterval <= 0) return;
|
|
98
|
+
const intervalId = setInterval(() => {
|
|
99
|
+
checkSession();
|
|
100
|
+
}, refreshInterval);
|
|
101
|
+
return () => clearInterval(intervalId);
|
|
102
|
+
}, [checkSession, refreshInterval]);
|
|
83
103
|
const logout = useCallback(async () => {
|
|
84
104
|
await client.logout();
|
|
85
105
|
setState({
|
|
@@ -98,6 +118,131 @@ function useAuth(config) {
|
|
|
98
118
|
};
|
|
99
119
|
}
|
|
100
120
|
|
|
121
|
+
// src/react/useHeadlessAuth.ts
|
|
122
|
+
import { useState as useState2, useCallback as useCallback2, useMemo as useMemo2 } from "react";
|
|
123
|
+
function useHeadlessAuth(options) {
|
|
124
|
+
const client = useMemo2(
|
|
125
|
+
() => createReauthClient({ domain: options.domain, timeout: options.timeout }),
|
|
126
|
+
[options.domain, options.timeout]
|
|
127
|
+
);
|
|
128
|
+
const [state, setState] = useState2({
|
|
129
|
+
loading: false,
|
|
130
|
+
error: null,
|
|
131
|
+
step: "idle",
|
|
132
|
+
config: null,
|
|
133
|
+
verifyResult: null
|
|
134
|
+
});
|
|
135
|
+
const getConfig = useCallback2(async () => {
|
|
136
|
+
setState((s) => ({ ...s, loading: true, error: null }));
|
|
137
|
+
try {
|
|
138
|
+
const config = await client.getConfig();
|
|
139
|
+
setState((s) => ({ ...s, loading: false, config }));
|
|
140
|
+
return config;
|
|
141
|
+
} catch (err) {
|
|
142
|
+
const message = err instanceof Error ? err.message : "Failed to get config";
|
|
143
|
+
setState((s) => ({ ...s, loading: false, error: message }));
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
}, [client]);
|
|
147
|
+
const requestMagicLink = useCallback2(
|
|
148
|
+
async (email) => {
|
|
149
|
+
setState((s) => ({ ...s, loading: true, error: null }));
|
|
150
|
+
try {
|
|
151
|
+
await client.requestMagicLink({
|
|
152
|
+
email,
|
|
153
|
+
callbackUrl: options.callbackUrl
|
|
154
|
+
});
|
|
155
|
+
setState((s) => ({
|
|
156
|
+
...s,
|
|
157
|
+
loading: false,
|
|
158
|
+
step: "magic_link_sent"
|
|
159
|
+
}));
|
|
160
|
+
} catch (err) {
|
|
161
|
+
const message = err instanceof Error ? err.message : "Failed to send magic link";
|
|
162
|
+
setState((s) => ({ ...s, loading: false, error: message }));
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
[client, options.callbackUrl]
|
|
167
|
+
);
|
|
168
|
+
const verifyMagicLink = useCallback2(
|
|
169
|
+
async (token) => {
|
|
170
|
+
setState((s) => ({ ...s, loading: true, error: null }));
|
|
171
|
+
try {
|
|
172
|
+
const result = await client.verifyMagicLink({ token });
|
|
173
|
+
setState((s) => ({
|
|
174
|
+
...s,
|
|
175
|
+
loading: false,
|
|
176
|
+
step: "completed",
|
|
177
|
+
verifyResult: result
|
|
178
|
+
}));
|
|
179
|
+
return result;
|
|
180
|
+
} catch (err) {
|
|
181
|
+
const message = err instanceof Error ? err.message : "Failed to verify magic link";
|
|
182
|
+
setState((s) => ({ ...s, loading: false, error: message }));
|
|
183
|
+
throw err;
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
[client]
|
|
187
|
+
);
|
|
188
|
+
const startGoogleOAuth = useCallback2(async () => {
|
|
189
|
+
setState((s) => ({ ...s, loading: true, error: null }));
|
|
190
|
+
try {
|
|
191
|
+
const result = await client.startGoogleOAuth();
|
|
192
|
+
setState((s) => ({
|
|
193
|
+
...s,
|
|
194
|
+
loading: false,
|
|
195
|
+
step: "google_started"
|
|
196
|
+
}));
|
|
197
|
+
if (typeof window !== "undefined") {
|
|
198
|
+
window.location.href = result.authUrl;
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
} catch (err) {
|
|
202
|
+
const message = err instanceof Error ? err.message : "Failed to start Google OAuth";
|
|
203
|
+
setState((s) => ({ ...s, loading: false, error: message }));
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
}, [client]);
|
|
207
|
+
const startTwitterOAuth = useCallback2(async () => {
|
|
208
|
+
setState((s) => ({ ...s, loading: true, error: null }));
|
|
209
|
+
try {
|
|
210
|
+
const result = await client.startTwitterOAuth();
|
|
211
|
+
setState((s) => ({
|
|
212
|
+
...s,
|
|
213
|
+
loading: false,
|
|
214
|
+
step: "twitter_started"
|
|
215
|
+
}));
|
|
216
|
+
if (typeof window !== "undefined") {
|
|
217
|
+
window.location.href = result.authUrl;
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
} catch (err) {
|
|
221
|
+
const message = err instanceof Error ? err.message : "Failed to start Twitter OAuth";
|
|
222
|
+
setState((s) => ({ ...s, loading: false, error: message }));
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
}, [client]);
|
|
226
|
+
const reset = useCallback2(() => {
|
|
227
|
+
setState({
|
|
228
|
+
loading: false,
|
|
229
|
+
error: null,
|
|
230
|
+
step: "idle",
|
|
231
|
+
config: null,
|
|
232
|
+
verifyResult: null
|
|
233
|
+
});
|
|
234
|
+
}, []);
|
|
235
|
+
return {
|
|
236
|
+
...state,
|
|
237
|
+
getConfig,
|
|
238
|
+
requestMagicLink,
|
|
239
|
+
verifyMagicLink,
|
|
240
|
+
startGoogleOAuth,
|
|
241
|
+
startTwitterOAuth,
|
|
242
|
+
reset
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
101
246
|
// src/react/AuthProvider.tsx
|
|
102
247
|
import { createContext, useContext } from "react";
|
|
103
248
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -115,7 +260,7 @@ function useAuthContext() {
|
|
|
115
260
|
}
|
|
116
261
|
|
|
117
262
|
// src/react/ProtectedRoute.tsx
|
|
118
|
-
import { useEffect as useEffect2 } from "react";
|
|
263
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
119
264
|
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
120
265
|
function ProtectedRoute({
|
|
121
266
|
children,
|
|
@@ -124,8 +269,10 @@ function ProtectedRoute({
|
|
|
124
269
|
onWaitlist
|
|
125
270
|
}) {
|
|
126
271
|
const { user, loading, isOnWaitlist, login } = useAuthContext();
|
|
272
|
+
const hasRedirected = useRef2(false);
|
|
127
273
|
useEffect2(() => {
|
|
128
|
-
if (!loading && !user) {
|
|
274
|
+
if (!loading && !user && !hasRedirected.current) {
|
|
275
|
+
hasRedirected.current = true;
|
|
129
276
|
if (onUnauthenticated) {
|
|
130
277
|
onUnauthenticated();
|
|
131
278
|
} else {
|
|
@@ -134,7 +281,8 @@ function ProtectedRoute({
|
|
|
134
281
|
}
|
|
135
282
|
}, [loading, user, login, onUnauthenticated]);
|
|
136
283
|
useEffect2(() => {
|
|
137
|
-
if (!loading && isOnWaitlist && onWaitlist) {
|
|
284
|
+
if (!loading && isOnWaitlist && onWaitlist && !hasRedirected.current) {
|
|
285
|
+
hasRedirected.current = true;
|
|
138
286
|
onWaitlist();
|
|
139
287
|
}
|
|
140
288
|
}, [loading, isOnWaitlist, onWaitlist]);
|
|
@@ -150,5 +298,6 @@ export {
|
|
|
150
298
|
AuthProvider,
|
|
151
299
|
ProtectedRoute,
|
|
152
300
|
useAuth,
|
|
153
|
-
useAuthContext
|
|
301
|
+
useAuthContext,
|
|
302
|
+
useHeadlessAuth
|
|
154
303
|
};
|
package/dist/server.d.mts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { q as ReauthServerConfig, r as AuthResult, s as RequestLike, p as UserDetails, x as ChargeOptions, y as DepositOptions, e as TransactionsPaginationOptions, B as BalanceTransaction } from './types-DKUKhCNE.mjs';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Derives a JWT signing secret from an API key using HKDF-SHA256.
|
|
5
|
+
* This must match the Rust backend implementation exactly.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: Returns hex-encoded string (64 chars), NOT raw bytes.
|
|
8
|
+
* The Rust API uses the ASCII bytes of the hex string as the JWT secret,
|
|
9
|
+
* so we must do the same for compatibility.
|
|
10
|
+
*
|
|
11
|
+
* @param apiKey - The raw API key (e.g., "sk_live_...")
|
|
12
|
+
* @param domainId - UUID of the domain (used as salt for domain isolation)
|
|
13
|
+
* @returns Promise<string> - 64-char hex string (to be used as ASCII bytes for JWT signing)
|
|
14
|
+
*/
|
|
15
|
+
declare function deriveJwtSecret(apiKey: string, domainId: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Parse cookies from a cookie header string.
|
|
18
|
+
* Handles URL encoding and multiple cookies properly.
|
|
19
|
+
*/
|
|
20
|
+
declare function parseCookies(cookieHeader: string): Record<string, string>;
|
|
3
21
|
/**
|
|
4
22
|
* Create a reauth client for server-side authentication.
|
|
5
23
|
* Uses local JWT verification for fast, reliable auth checks.
|
|
@@ -185,4 +203,4 @@ declare function createServerClient(config: ReauthServerConfig): {
|
|
|
185
203
|
};
|
|
186
204
|
type ServerClient = ReturnType<typeof createServerClient>;
|
|
187
205
|
|
|
188
|
-
export { type ServerClient, createServerClient };
|
|
206
|
+
export { type ServerClient, createServerClient, deriveJwtSecret, parseCookies };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { q as ReauthServerConfig, r as AuthResult, s as RequestLike, p as UserDetails, x as ChargeOptions, y as DepositOptions, e as TransactionsPaginationOptions, B as BalanceTransaction } from './types-DKUKhCNE.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Derives a JWT signing secret from an API key using HKDF-SHA256.
|
|
5
|
+
* This must match the Rust backend implementation exactly.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: Returns hex-encoded string (64 chars), NOT raw bytes.
|
|
8
|
+
* The Rust API uses the ASCII bytes of the hex string as the JWT secret,
|
|
9
|
+
* so we must do the same for compatibility.
|
|
10
|
+
*
|
|
11
|
+
* @param apiKey - The raw API key (e.g., "sk_live_...")
|
|
12
|
+
* @param domainId - UUID of the domain (used as salt for domain isolation)
|
|
13
|
+
* @returns Promise<string> - 64-char hex string (to be used as ASCII bytes for JWT signing)
|
|
14
|
+
*/
|
|
15
|
+
declare function deriveJwtSecret(apiKey: string, domainId: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Parse cookies from a cookie header string.
|
|
18
|
+
* Handles URL encoding and multiple cookies properly.
|
|
19
|
+
*/
|
|
20
|
+
declare function parseCookies(cookieHeader: string): Record<string, string>;
|
|
3
21
|
/**
|
|
4
22
|
* Create a reauth client for server-side authentication.
|
|
5
23
|
* Uses local JWT verification for fast, reliable auth checks.
|
|
@@ -185,4 +203,4 @@ declare function createServerClient(config: ReauthServerConfig): {
|
|
|
185
203
|
};
|
|
186
204
|
type ServerClient = ReturnType<typeof createServerClient>;
|
|
187
205
|
|
|
188
|
-
export { type ServerClient, createServerClient };
|
|
206
|
+
export { type ServerClient, createServerClient, deriveJwtSecret, parseCookies };
|
package/dist/server.js
CHANGED
|
@@ -30,11 +30,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/server.ts
|
|
31
31
|
var server_exports = {};
|
|
32
32
|
__export(server_exports, {
|
|
33
|
-
createServerClient: () => createServerClient
|
|
33
|
+
createServerClient: () => createServerClient,
|
|
34
|
+
deriveJwtSecret: () => deriveJwtSecret,
|
|
35
|
+
parseCookies: () => parseCookies
|
|
34
36
|
});
|
|
35
37
|
module.exports = __toCommonJS(server_exports);
|
|
36
38
|
var import_crypto = require("crypto");
|
|
37
39
|
var jose = __toESM(require("jose"));
|
|
40
|
+
|
|
41
|
+
// src/types.ts
|
|
42
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
43
|
+
|
|
44
|
+
// src/server.ts
|
|
38
45
|
async function deriveJwtSecret(apiKey, domainId) {
|
|
39
46
|
const salt = Buffer.from(domainId.replace(/-/g, ""), "hex");
|
|
40
47
|
const info = Buffer.from("reauth-jwt-v1");
|
|
@@ -45,6 +52,30 @@ async function deriveJwtSecret(apiKey, domainId) {
|
|
|
45
52
|
});
|
|
46
53
|
});
|
|
47
54
|
}
|
|
55
|
+
var VALID_SUBSCRIPTION_STATUSES = /* @__PURE__ */ new Set([
|
|
56
|
+
"active",
|
|
57
|
+
"past_due",
|
|
58
|
+
"canceled",
|
|
59
|
+
"trialing",
|
|
60
|
+
"incomplete",
|
|
61
|
+
"incomplete_expired",
|
|
62
|
+
"unpaid",
|
|
63
|
+
"paused",
|
|
64
|
+
"none"
|
|
65
|
+
]);
|
|
66
|
+
function isValidClaims(payload) {
|
|
67
|
+
if (typeof payload.sub !== "string") return false;
|
|
68
|
+
if (typeof payload.domain_id !== "string") return false;
|
|
69
|
+
if (typeof payload.domain !== "string") return false;
|
|
70
|
+
if (typeof payload.active_org_id !== "string") return false;
|
|
71
|
+
if (typeof payload.org_role !== "string") return false;
|
|
72
|
+
if (!Array.isArray(payload.roles) || !payload.roles.every((r) => typeof r === "string")) return false;
|
|
73
|
+
const subscription = payload.subscription;
|
|
74
|
+
if (typeof subscription !== "object" || subscription === null || Array.isArray(subscription)) return false;
|
|
75
|
+
const status = subscription.status;
|
|
76
|
+
if (typeof status !== "string" || !VALID_SUBSCRIPTION_STATUSES.has(status)) return false;
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
48
79
|
function transformSubscription(sub) {
|
|
49
80
|
return {
|
|
50
81
|
status: sub.status,
|
|
@@ -71,6 +102,10 @@ function parseCookies(cookieHeader) {
|
|
|
71
102
|
}
|
|
72
103
|
function createServerClient(config) {
|
|
73
104
|
const { domain, apiKey } = config;
|
|
105
|
+
if (config.timeout !== void 0 && (!Number.isFinite(config.timeout) || config.timeout <= 0)) {
|
|
106
|
+
throw new Error("timeout must be a positive finite number in milliseconds");
|
|
107
|
+
}
|
|
108
|
+
const timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
74
109
|
if (!apiKey) {
|
|
75
110
|
throw new Error(
|
|
76
111
|
"apiKey is required for createServerClient. Get one from the Reauth dashboard."
|
|
@@ -118,7 +153,11 @@ function createServerClient(config) {
|
|
|
118
153
|
// 60 seconds clock skew tolerance
|
|
119
154
|
}
|
|
120
155
|
);
|
|
121
|
-
const
|
|
156
|
+
const payloadRecord = payload;
|
|
157
|
+
if (!isValidClaims(payloadRecord)) {
|
|
158
|
+
return { valid: false, user: null, claims: null, error: "Invalid token claims: missing required fields" };
|
|
159
|
+
}
|
|
160
|
+
const claims = payloadRecord;
|
|
122
161
|
if (claims.domain !== domain) {
|
|
123
162
|
return { valid: false, user: null, claims: null, error: "Domain mismatch" };
|
|
124
163
|
}
|
|
@@ -127,6 +166,8 @@ function createServerClient(config) {
|
|
|
127
166
|
user: {
|
|
128
167
|
id: claims.sub,
|
|
129
168
|
roles: claims.roles,
|
|
169
|
+
activeOrgId: claims.active_org_id,
|
|
170
|
+
orgRole: claims.org_role,
|
|
130
171
|
subscription: transformSubscription(claims.subscription)
|
|
131
172
|
},
|
|
132
173
|
claims
|
|
@@ -241,10 +282,11 @@ function createServerClient(config) {
|
|
|
241
282
|
* ```
|
|
242
283
|
*/
|
|
243
284
|
async getUserById(userId) {
|
|
244
|
-
const res = await fetch(`${developerBaseUrl}/users/${userId}`, {
|
|
285
|
+
const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}`, {
|
|
245
286
|
headers: {
|
|
246
287
|
Authorization: `Bearer ${apiKey}`
|
|
247
|
-
}
|
|
288
|
+
},
|
|
289
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
248
290
|
});
|
|
249
291
|
if (!res.ok) {
|
|
250
292
|
if (res.status === 401) {
|
|
@@ -277,16 +319,18 @@ function createServerClient(config) {
|
|
|
277
319
|
* @returns Object with the current balance
|
|
278
320
|
*/
|
|
279
321
|
async getBalance(userId) {
|
|
280
|
-
const res = await fetch(`${developerBaseUrl}/users/${userId}/balance`, {
|
|
322
|
+
const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/balance`, {
|
|
281
323
|
headers: {
|
|
282
324
|
Authorization: `Bearer ${apiKey}`
|
|
283
|
-
}
|
|
325
|
+
},
|
|
326
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
284
327
|
});
|
|
285
328
|
if (!res.ok) {
|
|
286
329
|
if (res.status === 401) throw new Error("Invalid API key");
|
|
287
330
|
throw new Error(`Failed to get balance: ${res.status}`);
|
|
288
331
|
}
|
|
289
|
-
|
|
332
|
+
const data = await res.json();
|
|
333
|
+
return { balance: data.balance };
|
|
290
334
|
},
|
|
291
335
|
/**
|
|
292
336
|
* Charge (deduct) credits from a user's balance.
|
|
@@ -297,12 +341,13 @@ function createServerClient(config) {
|
|
|
297
341
|
* @throws Error with status 402 if insufficient balance, 400 if invalid amount
|
|
298
342
|
*/
|
|
299
343
|
async charge(userId, opts) {
|
|
300
|
-
const res = await fetch(`${developerBaseUrl}/users/${userId}/charge`, {
|
|
344
|
+
const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/charge`, {
|
|
301
345
|
method: "POST",
|
|
302
346
|
headers: {
|
|
303
347
|
"Content-Type": "application/json",
|
|
304
348
|
Authorization: `Bearer ${apiKey}`
|
|
305
349
|
},
|
|
350
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
306
351
|
body: JSON.stringify({
|
|
307
352
|
amount: opts.amount,
|
|
308
353
|
request_uuid: opts.requestUuid,
|
|
@@ -326,12 +371,13 @@ function createServerClient(config) {
|
|
|
326
371
|
* @returns Object with the new balance after deposit
|
|
327
372
|
*/
|
|
328
373
|
async deposit(userId, opts) {
|
|
329
|
-
const res = await fetch(`${developerBaseUrl}/users/${userId}/deposit`, {
|
|
374
|
+
const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/deposit`, {
|
|
330
375
|
method: "POST",
|
|
331
376
|
headers: {
|
|
332
377
|
"Content-Type": "application/json",
|
|
333
378
|
Authorization: `Bearer ${apiKey}`
|
|
334
379
|
},
|
|
380
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
335
381
|
body: JSON.stringify({
|
|
336
382
|
amount: opts.amount,
|
|
337
383
|
request_uuid: opts.requestUuid,
|
|
@@ -359,11 +405,12 @@ function createServerClient(config) {
|
|
|
359
405
|
if (opts?.offset !== void 0) params.set("offset", String(opts.offset));
|
|
360
406
|
const qs = params.toString();
|
|
361
407
|
const res = await fetch(
|
|
362
|
-
`${developerBaseUrl}/users/${userId}/balance/transactions${qs ? `?${qs}` : ""}`,
|
|
408
|
+
`${developerBaseUrl}/users/${encodeURIComponent(userId)}/balance/transactions${qs ? `?${qs}` : ""}`,
|
|
363
409
|
{
|
|
364
410
|
headers: {
|
|
365
411
|
Authorization: `Bearer ${apiKey}`
|
|
366
|
-
}
|
|
412
|
+
},
|
|
413
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
367
414
|
}
|
|
368
415
|
);
|
|
369
416
|
if (!res.ok) {
|
|
@@ -387,5 +434,7 @@ function createServerClient(config) {
|
|
|
387
434
|
}
|
|
388
435
|
// Annotate the CommonJS export names for ESM import in node:
|
|
389
436
|
0 && (module.exports = {
|
|
390
|
-
createServerClient
|
|
437
|
+
createServerClient,
|
|
438
|
+
deriveJwtSecret,
|
|
439
|
+
parseCookies
|
|
391
440
|
});
|
package/dist/server.mjs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_TIMEOUT_MS
|
|
3
|
+
} from "./chunk-EY5LQCDG.mjs";
|
|
4
|
+
|
|
1
5
|
// src/server.ts
|
|
2
6
|
import { hkdf } from "crypto";
|
|
3
7
|
import * as jose from "jose";
|
|
@@ -11,6 +15,30 @@ async function deriveJwtSecret(apiKey, domainId) {
|
|
|
11
15
|
});
|
|
12
16
|
});
|
|
13
17
|
}
|
|
18
|
+
var VALID_SUBSCRIPTION_STATUSES = /* @__PURE__ */ new Set([
|
|
19
|
+
"active",
|
|
20
|
+
"past_due",
|
|
21
|
+
"canceled",
|
|
22
|
+
"trialing",
|
|
23
|
+
"incomplete",
|
|
24
|
+
"incomplete_expired",
|
|
25
|
+
"unpaid",
|
|
26
|
+
"paused",
|
|
27
|
+
"none"
|
|
28
|
+
]);
|
|
29
|
+
function isValidClaims(payload) {
|
|
30
|
+
if (typeof payload.sub !== "string") return false;
|
|
31
|
+
if (typeof payload.domain_id !== "string") return false;
|
|
32
|
+
if (typeof payload.domain !== "string") return false;
|
|
33
|
+
if (typeof payload.active_org_id !== "string") return false;
|
|
34
|
+
if (typeof payload.org_role !== "string") return false;
|
|
35
|
+
if (!Array.isArray(payload.roles) || !payload.roles.every((r) => typeof r === "string")) return false;
|
|
36
|
+
const subscription = payload.subscription;
|
|
37
|
+
if (typeof subscription !== "object" || subscription === null || Array.isArray(subscription)) return false;
|
|
38
|
+
const status = subscription.status;
|
|
39
|
+
if (typeof status !== "string" || !VALID_SUBSCRIPTION_STATUSES.has(status)) return false;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
14
42
|
function transformSubscription(sub) {
|
|
15
43
|
return {
|
|
16
44
|
status: sub.status,
|
|
@@ -37,6 +65,10 @@ function parseCookies(cookieHeader) {
|
|
|
37
65
|
}
|
|
38
66
|
function createServerClient(config) {
|
|
39
67
|
const { domain, apiKey } = config;
|
|
68
|
+
if (config.timeout !== void 0 && (!Number.isFinite(config.timeout) || config.timeout <= 0)) {
|
|
69
|
+
throw new Error("timeout must be a positive finite number in milliseconds");
|
|
70
|
+
}
|
|
71
|
+
const timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
40
72
|
if (!apiKey) {
|
|
41
73
|
throw new Error(
|
|
42
74
|
"apiKey is required for createServerClient. Get one from the Reauth dashboard."
|
|
@@ -84,7 +116,11 @@ function createServerClient(config) {
|
|
|
84
116
|
// 60 seconds clock skew tolerance
|
|
85
117
|
}
|
|
86
118
|
);
|
|
87
|
-
const
|
|
119
|
+
const payloadRecord = payload;
|
|
120
|
+
if (!isValidClaims(payloadRecord)) {
|
|
121
|
+
return { valid: false, user: null, claims: null, error: "Invalid token claims: missing required fields" };
|
|
122
|
+
}
|
|
123
|
+
const claims = payloadRecord;
|
|
88
124
|
if (claims.domain !== domain) {
|
|
89
125
|
return { valid: false, user: null, claims: null, error: "Domain mismatch" };
|
|
90
126
|
}
|
|
@@ -93,6 +129,8 @@ function createServerClient(config) {
|
|
|
93
129
|
user: {
|
|
94
130
|
id: claims.sub,
|
|
95
131
|
roles: claims.roles,
|
|
132
|
+
activeOrgId: claims.active_org_id,
|
|
133
|
+
orgRole: claims.org_role,
|
|
96
134
|
subscription: transformSubscription(claims.subscription)
|
|
97
135
|
},
|
|
98
136
|
claims
|
|
@@ -207,10 +245,11 @@ function createServerClient(config) {
|
|
|
207
245
|
* ```
|
|
208
246
|
*/
|
|
209
247
|
async getUserById(userId) {
|
|
210
|
-
const res = await fetch(`${developerBaseUrl}/users/${userId}`, {
|
|
248
|
+
const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}`, {
|
|
211
249
|
headers: {
|
|
212
250
|
Authorization: `Bearer ${apiKey}`
|
|
213
|
-
}
|
|
251
|
+
},
|
|
252
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
214
253
|
});
|
|
215
254
|
if (!res.ok) {
|
|
216
255
|
if (res.status === 401) {
|
|
@@ -243,16 +282,18 @@ function createServerClient(config) {
|
|
|
243
282
|
* @returns Object with the current balance
|
|
244
283
|
*/
|
|
245
284
|
async getBalance(userId) {
|
|
246
|
-
const res = await fetch(`${developerBaseUrl}/users/${userId}/balance`, {
|
|
285
|
+
const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/balance`, {
|
|
247
286
|
headers: {
|
|
248
287
|
Authorization: `Bearer ${apiKey}`
|
|
249
|
-
}
|
|
288
|
+
},
|
|
289
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
250
290
|
});
|
|
251
291
|
if (!res.ok) {
|
|
252
292
|
if (res.status === 401) throw new Error("Invalid API key");
|
|
253
293
|
throw new Error(`Failed to get balance: ${res.status}`);
|
|
254
294
|
}
|
|
255
|
-
|
|
295
|
+
const data = await res.json();
|
|
296
|
+
return { balance: data.balance };
|
|
256
297
|
},
|
|
257
298
|
/**
|
|
258
299
|
* Charge (deduct) credits from a user's balance.
|
|
@@ -263,12 +304,13 @@ function createServerClient(config) {
|
|
|
263
304
|
* @throws Error with status 402 if insufficient balance, 400 if invalid amount
|
|
264
305
|
*/
|
|
265
306
|
async charge(userId, opts) {
|
|
266
|
-
const res = await fetch(`${developerBaseUrl}/users/${userId}/charge`, {
|
|
307
|
+
const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/charge`, {
|
|
267
308
|
method: "POST",
|
|
268
309
|
headers: {
|
|
269
310
|
"Content-Type": "application/json",
|
|
270
311
|
Authorization: `Bearer ${apiKey}`
|
|
271
312
|
},
|
|
313
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
272
314
|
body: JSON.stringify({
|
|
273
315
|
amount: opts.amount,
|
|
274
316
|
request_uuid: opts.requestUuid,
|
|
@@ -292,12 +334,13 @@ function createServerClient(config) {
|
|
|
292
334
|
* @returns Object with the new balance after deposit
|
|
293
335
|
*/
|
|
294
336
|
async deposit(userId, opts) {
|
|
295
|
-
const res = await fetch(`${developerBaseUrl}/users/${userId}/deposit`, {
|
|
337
|
+
const res = await fetch(`${developerBaseUrl}/users/${encodeURIComponent(userId)}/deposit`, {
|
|
296
338
|
method: "POST",
|
|
297
339
|
headers: {
|
|
298
340
|
"Content-Type": "application/json",
|
|
299
341
|
Authorization: `Bearer ${apiKey}`
|
|
300
342
|
},
|
|
343
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
301
344
|
body: JSON.stringify({
|
|
302
345
|
amount: opts.amount,
|
|
303
346
|
request_uuid: opts.requestUuid,
|
|
@@ -325,11 +368,12 @@ function createServerClient(config) {
|
|
|
325
368
|
if (opts?.offset !== void 0) params.set("offset", String(opts.offset));
|
|
326
369
|
const qs = params.toString();
|
|
327
370
|
const res = await fetch(
|
|
328
|
-
`${developerBaseUrl}/users/${userId}/balance/transactions${qs ? `?${qs}` : ""}`,
|
|
371
|
+
`${developerBaseUrl}/users/${encodeURIComponent(userId)}/balance/transactions${qs ? `?${qs}` : ""}`,
|
|
329
372
|
{
|
|
330
373
|
headers: {
|
|
331
374
|
Authorization: `Bearer ${apiKey}`
|
|
332
|
-
}
|
|
375
|
+
},
|
|
376
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
333
377
|
}
|
|
334
378
|
);
|
|
335
379
|
if (!res.ok) {
|
|
@@ -352,5 +396,7 @@ function createServerClient(config) {
|
|
|
352
396
|
};
|
|
353
397
|
}
|
|
354
398
|
export {
|
|
355
|
-
createServerClient
|
|
399
|
+
createServerClient,
|
|
400
|
+
deriveJwtSecret,
|
|
401
|
+
parseCookies
|
|
356
402
|
};
|