@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/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/chunk-JX2J36FS.mjs +269 -0
- package/dist/index.d.mts +127 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +308 -0
- package/dist/index.mjs +17 -0
- package/dist/react/index.d.mts +123 -0
- package/dist/react/index.d.ts +123 -0
- package/dist/react/index.js +448 -0
- package/dist/react/index.mjs +154 -0
- package/dist/server.d.mts +188 -0
- package/dist/server.d.ts +188 -0
- package/dist/server.js +391 -0
- package/dist/server.mjs +356 -0
- package/dist/types-D8oOYbeC.d.mts +169 -0
- package/dist/types-D8oOYbeC.d.ts +169 -0
- package/dist/webhooks.d.mts +23 -0
- package/dist/webhooks.d.ts +23 -0
- package/dist/webhooks.js +123 -0
- package/dist/webhooks.mjs +94 -0
- package/package.json +78 -0
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
|
+
});
|