@reauth-dev/sdk 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/server.js ADDED
@@ -0,0 +1,391 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/server.ts
31
+ var server_exports = {};
32
+ __export(server_exports, {
33
+ createServerClient: () => createServerClient
34
+ });
35
+ module.exports = __toCommonJS(server_exports);
36
+ var import_crypto = require("crypto");
37
+ var jose = __toESM(require("jose"));
38
+ async function deriveJwtSecret(apiKey, domainId) {
39
+ const salt = Buffer.from(domainId.replace(/-/g, ""), "hex");
40
+ const info = Buffer.from("reauth-jwt-v1");
41
+ return new Promise((resolve, reject) => {
42
+ (0, import_crypto.hkdf)("sha256", apiKey, salt, info, 32, (err, derivedKey) => {
43
+ if (err) reject(err);
44
+ else resolve(Buffer.from(derivedKey).toString("hex"));
45
+ });
46
+ });
47
+ }
48
+ function transformSubscription(sub) {
49
+ return {
50
+ status: sub.status,
51
+ planCode: sub.plan_code,
52
+ planName: sub.plan_name,
53
+ currentPeriodEnd: sub.current_period_end,
54
+ cancelAtPeriodEnd: sub.cancel_at_period_end,
55
+ trialEndsAt: sub.trial_ends_at
56
+ };
57
+ }
58
+ function parseCookies(cookieHeader) {
59
+ const cookies = {};
60
+ for (const cookie of cookieHeader.split(";")) {
61
+ const [key, ...valueParts] = cookie.trim().split("=");
62
+ if (key) {
63
+ try {
64
+ cookies[key] = decodeURIComponent(valueParts.join("="));
65
+ } catch {
66
+ cookies[key] = valueParts.join("=");
67
+ }
68
+ }
69
+ }
70
+ return cookies;
71
+ }
72
+ function createServerClient(config) {
73
+ const { domain, apiKey } = config;
74
+ if (!apiKey) {
75
+ throw new Error(
76
+ "apiKey is required for createServerClient. Get one from the Reauth dashboard."
77
+ );
78
+ }
79
+ const developerBaseUrl = `https://reauth.${domain}/api/developer`;
80
+ return {
81
+ /**
82
+ * Verify a JWT token locally using HKDF-derived secret.
83
+ * No network call required - fast and reliable.
84
+ *
85
+ * The domain_id is extracted from the token claims automatically.
86
+ *
87
+ * @param token - The JWT token to verify
88
+ * @returns AuthResult with user info from claims
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const result = await reauth.verifyToken(token);
93
+ * if (result.valid && result.user) {
94
+ * console.log('User ID:', result.user.id);
95
+ * console.log('Roles:', result.user.roles);
96
+ * console.log('Subscription:', result.user.subscription);
97
+ * }
98
+ * ```
99
+ */
100
+ async verifyToken(token) {
101
+ try {
102
+ const unverified = jose.decodeJwt(token);
103
+ const domainId = unverified.domain_id;
104
+ const unverifiedDomain = unverified.domain;
105
+ if (!domainId) {
106
+ return { valid: false, user: null, claims: null, error: "Missing domain_id in token" };
107
+ }
108
+ if (unverifiedDomain !== domain) {
109
+ return { valid: false, user: null, claims: null, error: "Domain mismatch" };
110
+ }
111
+ const secret = await deriveJwtSecret(apiKey, domainId);
112
+ const { payload } = await jose.jwtVerify(
113
+ token,
114
+ new TextEncoder().encode(secret),
115
+ {
116
+ algorithms: ["HS256"],
117
+ clockTolerance: 60
118
+ // 60 seconds clock skew tolerance
119
+ }
120
+ );
121
+ const claims = payload;
122
+ if (claims.domain !== domain) {
123
+ return { valid: false, user: null, claims: null, error: "Domain mismatch" };
124
+ }
125
+ return {
126
+ valid: true,
127
+ user: {
128
+ id: claims.sub,
129
+ roles: claims.roles,
130
+ subscription: transformSubscription(claims.subscription)
131
+ },
132
+ claims
133
+ };
134
+ } catch (err) {
135
+ const error = err instanceof Error ? err.message : "Unknown error";
136
+ return { valid: false, user: null, claims: null, error };
137
+ }
138
+ },
139
+ /**
140
+ * Extract a token from a request object.
141
+ * Tries Authorization: Bearer header first, then falls back to cookies.
142
+ *
143
+ * @param request - Object with headers (authorization and/or cookie)
144
+ * @returns The token string or null if not found
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * const token = reauth.extractToken({
149
+ * headers: {
150
+ * authorization: req.headers.authorization,
151
+ * cookie: req.headers.cookie,
152
+ * },
153
+ * });
154
+ * ```
155
+ */
156
+ extractToken(request) {
157
+ const authHeader = request.headers?.authorization;
158
+ if (authHeader?.startsWith("Bearer ")) {
159
+ return authHeader.slice(7);
160
+ }
161
+ const cookieHeader = request.headers?.cookie;
162
+ if (cookieHeader) {
163
+ const cookies = parseCookies(cookieHeader);
164
+ if (cookies["end_user_access_token"]) {
165
+ return cookies["end_user_access_token"];
166
+ }
167
+ }
168
+ return null;
169
+ },
170
+ /**
171
+ * Authenticate a request by extracting and verifying the token.
172
+ * This is a convenience method combining extractToken and verifyToken.
173
+ *
174
+ * @param request - Object with headers (authorization and/or cookie)
175
+ * @returns AuthResult with user info from claims
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * // Express/Node.js
180
+ * async function authMiddleware(req, res, next) {
181
+ * const result = await reauth.authenticate({
182
+ * headers: {
183
+ * authorization: req.headers.authorization,
184
+ * cookie: req.headers.cookie,
185
+ * },
186
+ * });
187
+ *
188
+ * if (!result.valid || !result.user) {
189
+ * res.status(401).json({ error: result.error || 'Unauthorized' });
190
+ * return;
191
+ * }
192
+ *
193
+ * req.user = result.user;
194
+ * next();
195
+ * }
196
+ *
197
+ * // Next.js App Router
198
+ * export async function GET(request: NextRequest) {
199
+ * const result = await reauth.authenticate({
200
+ * headers: {
201
+ * authorization: request.headers.get('authorization') ?? undefined,
202
+ * cookie: request.headers.get('cookie') ?? undefined,
203
+ * },
204
+ * });
205
+ *
206
+ * if (!result.valid) {
207
+ * return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
208
+ * }
209
+ *
210
+ * return NextResponse.json({ user: result.user });
211
+ * }
212
+ * ```
213
+ */
214
+ async authenticate(request) {
215
+ const token = this.extractToken(request);
216
+ if (!token) {
217
+ return { valid: false, user: null, claims: null, error: "No token provided" };
218
+ }
219
+ return this.verifyToken(token);
220
+ },
221
+ /**
222
+ * Get user details by ID from the backend.
223
+ * Use this when you need full user info like email, frozen status, etc.
224
+ * that isn't available in the JWT claims.
225
+ *
226
+ * @param userId - The user ID to fetch
227
+ * @returns UserDetails or null if not found
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * // After verifying token, fetch full user details if needed
232
+ * const result = await reauth.authenticate(request);
233
+ * if (result.valid && result.user) {
234
+ * // If you need email or other details not in JWT
235
+ * const details = await reauth.getUserById(result.user.id);
236
+ * if (details) {
237
+ * console.log('Email:', details.email);
238
+ * console.log('Frozen:', details.isFrozen);
239
+ * }
240
+ * }
241
+ * ```
242
+ */
243
+ async getUserById(userId) {
244
+ const res = await fetch(`${developerBaseUrl}/users/${userId}`, {
245
+ headers: {
246
+ Authorization: `Bearer ${apiKey}`
247
+ }
248
+ });
249
+ if (!res.ok) {
250
+ if (res.status === 401) {
251
+ throw new Error("Invalid API key");
252
+ }
253
+ if (res.status === 404) {
254
+ return null;
255
+ }
256
+ throw new Error(`Failed to get user: ${res.status}`);
257
+ }
258
+ const data = await res.json();
259
+ return {
260
+ id: data.id,
261
+ email: data.email,
262
+ roles: data.roles,
263
+ emailVerifiedAt: data.email_verified_at,
264
+ lastLoginAt: data.last_login_at,
265
+ isFrozen: data.is_frozen,
266
+ isWhitelisted: data.is_whitelisted,
267
+ createdAt: data.created_at
268
+ };
269
+ },
270
+ // ========================================================================
271
+ // Balance Methods
272
+ // ========================================================================
273
+ /**
274
+ * Get a user's current balance.
275
+ *
276
+ * @param userId - The user ID to check
277
+ * @returns Object with the current balance
278
+ */
279
+ async getBalance(userId) {
280
+ const res = await fetch(`${developerBaseUrl}/users/${userId}/balance`, {
281
+ headers: {
282
+ Authorization: `Bearer ${apiKey}`
283
+ }
284
+ });
285
+ if (!res.ok) {
286
+ if (res.status === 401) throw new Error("Invalid API key");
287
+ throw new Error(`Failed to get balance: ${res.status}`);
288
+ }
289
+ return res.json();
290
+ },
291
+ /**
292
+ * Charge (deduct) credits from a user's balance.
293
+ *
294
+ * @param userId - The user ID to charge
295
+ * @param opts - Charge options (amount, requestUuid for idempotency, optional note)
296
+ * @returns Object with the new balance after charge
297
+ * @throws Error with status 402 if insufficient balance, 400 if invalid amount
298
+ */
299
+ async charge(userId, opts) {
300
+ const res = await fetch(`${developerBaseUrl}/users/${userId}/charge`, {
301
+ method: "POST",
302
+ headers: {
303
+ "Content-Type": "application/json",
304
+ Authorization: `Bearer ${apiKey}`
305
+ },
306
+ body: JSON.stringify({
307
+ amount: opts.amount,
308
+ request_uuid: opts.requestUuid,
309
+ note: opts.note
310
+ })
311
+ });
312
+ if (!res.ok) {
313
+ if (res.status === 401) throw new Error("Invalid API key");
314
+ if (res.status === 402) throw new Error("Insufficient balance");
315
+ if (res.status === 400) throw new Error("Invalid amount");
316
+ throw new Error(`Failed to charge balance: ${res.status}`);
317
+ }
318
+ const data = await res.json();
319
+ return { newBalance: data.new_balance };
320
+ },
321
+ /**
322
+ * Deposit (add) credits to a user's balance.
323
+ *
324
+ * @param userId - The user ID to deposit to
325
+ * @param opts - Deposit options (amount, requestUuid for idempotency, optional note)
326
+ * @returns Object with the new balance after deposit
327
+ */
328
+ async deposit(userId, opts) {
329
+ const res = await fetch(`${developerBaseUrl}/users/${userId}/deposit`, {
330
+ method: "POST",
331
+ headers: {
332
+ "Content-Type": "application/json",
333
+ Authorization: `Bearer ${apiKey}`
334
+ },
335
+ body: JSON.stringify({
336
+ amount: opts.amount,
337
+ request_uuid: opts.requestUuid,
338
+ note: opts.note
339
+ })
340
+ });
341
+ if (!res.ok) {
342
+ if (res.status === 401) throw new Error("Invalid API key");
343
+ if (res.status === 400) throw new Error("Invalid amount");
344
+ throw new Error(`Failed to deposit balance: ${res.status}`);
345
+ }
346
+ const data = await res.json();
347
+ return { newBalance: data.new_balance };
348
+ },
349
+ /**
350
+ * Get a user's balance transaction history.
351
+ *
352
+ * @param userId - The user ID to get transactions for
353
+ * @param opts - Optional pagination (limit, offset)
354
+ * @returns Object with array of transactions (newest first)
355
+ */
356
+ async getTransactions(userId, opts) {
357
+ const params = new URLSearchParams();
358
+ if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
359
+ if (opts?.offset !== void 0) params.set("offset", String(opts.offset));
360
+ const qs = params.toString();
361
+ const res = await fetch(
362
+ `${developerBaseUrl}/users/${userId}/balance/transactions${qs ? `?${qs}` : ""}`,
363
+ {
364
+ headers: {
365
+ Authorization: `Bearer ${apiKey}`
366
+ }
367
+ }
368
+ );
369
+ if (!res.ok) {
370
+ if (res.status === 401) throw new Error("Invalid API key");
371
+ throw new Error(`Failed to get transactions: ${res.status}`);
372
+ }
373
+ const data = await res.json();
374
+ return {
375
+ transactions: data.transactions.map(
376
+ (t) => ({
377
+ id: t.id,
378
+ amountDelta: t.amount_delta,
379
+ reason: t.reason,
380
+ balanceAfter: t.balance_after,
381
+ createdAt: t.created_at
382
+ })
383
+ )
384
+ };
385
+ }
386
+ };
387
+ }
388
+ // Annotate the CommonJS export names for ESM import in node:
389
+ 0 && (module.exports = {
390
+ createServerClient
391
+ });