@guren/server 1.0.0-rc.17 → 1.0.0-rc.18

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.
Files changed (46) hide show
  1. package/dist/McpServiceProvider-JW6PDVMD.js +7 -0
  2. package/dist/auth/index.js +59 -1549
  3. package/dist/authorization/index.js +19 -620
  4. package/dist/broadcasting/index.js +12 -895
  5. package/dist/cache/index.js +8 -809
  6. package/dist/chunk-2IP4LFVT.js +923 -0
  7. package/dist/chunk-74HTZG3V.js +331 -0
  8. package/dist/chunk-A2AZXY4M.js +598 -0
  9. package/dist/chunk-BJXAHGDR.js +1517 -0
  10. package/dist/chunk-CSDKWLFD.js +652 -0
  11. package/dist/chunk-CSRQTEQA.js +839 -0
  12. package/dist/chunk-DAQKYKLH.js +182 -0
  13. package/dist/chunk-EDRGAM6G.js +647 -0
  14. package/dist/chunk-EGU5KB7V.js +818 -0
  15. package/dist/chunk-H32L2NE3.js +372 -0
  16. package/dist/chunk-HKQSAFSN.js +837 -0
  17. package/dist/chunk-IOTWFHZU.js +558 -0
  18. package/dist/chunk-ONSDE37A.js +125 -0
  19. package/dist/chunk-QQKTH5KX.js +114 -0
  20. package/dist/chunk-R2TCP7D7.js +409 -0
  21. package/dist/chunk-SIP34GBE.js +380 -0
  22. package/dist/chunk-THSX7OOR.js +454 -0
  23. package/dist/chunk-VT5KRDPH.js +134 -0
  24. package/dist/chunk-WJJ5CTNI.js +907 -0
  25. package/dist/chunk-WVIWRSL3.js +248 -0
  26. package/dist/chunk-WVY45EIW.js +359 -0
  27. package/dist/chunk-ZRBLZY3M.js +462 -0
  28. package/dist/encryption/index.js +48 -556
  29. package/dist/errors-JOOPDDQ6.js +34 -0
  30. package/dist/events/index.js +14 -316
  31. package/dist/health/index.js +12 -367
  32. package/dist/i18n/index.js +14 -583
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +2551 -13886
  35. package/dist/lambda/index.js +4 -9
  36. package/dist/logging/index.js +12 -545
  37. package/dist/mail/index.d.ts +20 -1
  38. package/dist/mail/index.js +15 -694
  39. package/dist/mcp/index.js +5 -378
  40. package/dist/notifications/index.js +13 -730
  41. package/dist/queue/index.d.ts +29 -1
  42. package/dist/queue/index.js +22 -940
  43. package/dist/runtime/index.js +10 -258
  44. package/dist/scheduling/index.js +14 -822
  45. package/dist/storage/index.js +6 -830
  46. package/package.json +3 -3
@@ -1,1553 +1,63 @@
1
- // src/http/middleware/session.ts
2
- import { getCookie, setCookie, deleteCookie } from "hono/cookie";
3
- import { createHmac, timingSafeEqual } from "crypto";
4
-
5
- // src/encryption/app-key.ts
6
- import { hkdfSync, randomBytes } from "crypto";
7
- var APP_KEY_PREFIX = "base64:";
8
- var APP_KEY_BYTES = 32;
9
- function decodeAppKey(rawKey, envName) {
10
- const trimmed = rawKey.trim();
11
- if (!trimmed) {
12
- throw new Error(`${envName} is required and must be a base64-encoded 32-byte key.`);
13
- }
14
- const encoded = trimmed.startsWith(APP_KEY_PREFIX) ? trimmed.slice(APP_KEY_PREFIX.length) : trimmed;
15
- const decoded = Buffer.from(encoded, "base64");
16
- if (decoded.length !== APP_KEY_BYTES || decoded.toString("base64") !== encoded.replace(/\s+/gu, "")) {
17
- throw new Error(`${envName} must be a base64-encoded 32-byte key.`);
18
- }
19
- return decoded;
20
- }
21
- function parseAppKey(rawKey, envName = "APP_KEY") {
22
- return decodeAppKey(rawKey, envName);
23
- }
24
- function parsePreviousAppKeys(rawKeys, envName = "APP_PREVIOUS_KEYS") {
25
- if (!rawKeys) {
26
- return [];
27
- }
28
- return rawKeys.split(",").map((value) => value.trim()).filter(Boolean).map((value, index) => decodeAppKey(value, `${envName}[${index}]`));
29
- }
30
- function getAppKeyringFromEnv(env = process.env) {
31
- return {
32
- current: parseAppKey(env.APP_KEY ?? "", "APP_KEY"),
33
- previous: parsePreviousAppKeys(env.APP_PREVIOUS_KEYS)
34
- };
35
- }
36
- function deriveAppKey(rootKey, purpose) {
37
- return Buffer.from(
38
- hkdfSync("sha256", rootKey, Buffer.alloc(0), Buffer.from(`guren:${purpose}`, "utf8"), APP_KEY_BYTES)
39
- );
40
- }
41
- function deriveAppKeyring(keyring, purpose) {
42
- return {
43
- current: deriveAppKey(keyring.current, purpose),
44
- previous: keyring.previous.map((key) => deriveAppKey(key, purpose))
45
- };
46
- }
47
-
48
- // src/http/middleware/session.ts
49
- var DEFAULT_TTL_SECONDS = 60 * 60 * 2;
50
- var DEFAULT_COOKIE_SECURE = typeof process !== "undefined" ? process.env.NODE_ENV === "production" : true;
51
- var SESSION_CONTEXT_KEY = "guren:session";
52
- function getSessionFromContext(ctx) {
53
- return ctx.get(SESSION_CONTEXT_KEY);
54
- }
55
-
56
- // src/errors/HttpException.ts
57
- var HttpException = class _HttpException extends Error {
58
- /**
59
- * HTTP status code.
60
- */
61
- statusCode;
62
- /**
63
- * Validation or field-specific errors.
64
- */
65
- errors;
66
- /**
67
- * Additional error data.
68
- */
69
- data;
70
- constructor(statusCode, message, errors, data) {
71
- super(message);
72
- this.name = this.constructor.name;
73
- this.statusCode = statusCode;
74
- this.errors = errors;
75
- this.data = data;
76
- if (Error.captureStackTrace) {
77
- Error.captureStackTrace(this, this.constructor);
78
- }
79
- }
80
- /**
81
- * Convert to response format.
82
- */
83
- toResponse(debug = false) {
84
- const body = {
85
- message: this.message
86
- };
87
- if (this.errors) {
88
- body.errors = this.errors;
89
- }
90
- if (debug) {
91
- body.exception = this.name;
92
- body.stack = this.stack;
93
- }
94
- return {
95
- status: this.statusCode,
96
- body
97
- };
98
- }
99
- /**
100
- * Convert to JSON.
101
- */
102
- toJSON() {
103
- return {
104
- name: this.name,
105
- message: this.message,
106
- statusCode: this.statusCode,
107
- errors: this.errors,
108
- data: this.data
109
- };
110
- }
111
- // Factory methods
112
- /**
113
- * Create a 400 Bad Request exception.
114
- */
115
- static badRequest(message = "Bad Request") {
116
- return new _HttpException(400, message);
117
- }
118
- /**
119
- * Create a 401 Unauthorized exception.
120
- */
121
- static unauthorized(message = "Unauthorized") {
122
- return new _HttpException(401, message);
123
- }
124
- /**
125
- * Create a 403 Forbidden exception.
126
- */
127
- static forbidden(message = "Forbidden") {
128
- return new _HttpException(403, message);
129
- }
130
- /**
131
- * Create a 404 Not Found exception.
132
- */
133
- static notFound(message = "Not Found") {
134
- return new _HttpException(404, message);
135
- }
136
- /**
137
- * Create a 405 Method Not Allowed exception.
138
- */
139
- static methodNotAllowed(message = "Method Not Allowed") {
140
- return new _HttpException(405, message);
141
- }
142
- /**
143
- * Create a 409 Conflict exception.
144
- */
145
- static conflict(message = "Conflict") {
146
- return new _HttpException(409, message);
147
- }
148
- /**
149
- * Create a 410 Gone exception.
150
- */
151
- static gone(message = "Gone") {
152
- return new _HttpException(410, message);
153
- }
154
- /**
155
- * Create a 422 Unprocessable Entity exception.
156
- */
157
- static unprocessable(message = "Unprocessable Entity", errors) {
158
- return new _HttpException(422, message, errors);
159
- }
160
- /**
161
- * Create a 429 Too Many Requests exception.
162
- */
163
- static tooManyRequests(message = "Too Many Requests") {
164
- return new _HttpException(429, message);
165
- }
166
- /**
167
- * Create a 500 Internal Server Error exception.
168
- */
169
- static internal(message = "Internal Server Error") {
170
- return new _HttpException(500, message);
171
- }
172
- /**
173
- * Create a 501 Not Implemented exception.
174
- */
175
- static notImplemented(message = "Not Implemented") {
176
- return new _HttpException(501, message);
177
- }
178
- /**
179
- * Create a 502 Bad Gateway exception.
180
- */
181
- static badGateway(message = "Bad Gateway") {
182
- return new _HttpException(502, message);
183
- }
184
- /**
185
- * Create a 503 Service Unavailable exception.
186
- */
187
- static serviceUnavailable(message = "Service Unavailable") {
188
- return new _HttpException(503, message);
189
- }
190
- /**
191
- * Create a 504 Gateway Timeout exception.
192
- */
193
- static gatewayTimeout(message = "Gateway Timeout") {
194
- return new _HttpException(504, message);
195
- }
196
- /**
197
- * Check if an error is an HTTP exception.
198
- */
199
- static isHttpException(error) {
200
- return error instanceof _HttpException || typeof error === "object" && error !== null && "statusCode" in error && typeof error.statusCode === "number" && "toResponse" in error && typeof error.toResponse === "function";
201
- }
202
- };
203
-
204
- // src/errors/exceptions/AuthenticationException.ts
205
- var AuthenticationException = class _AuthenticationException extends HttpException {
206
- /**
207
- * The guard that threw the exception.
208
- */
209
- guard;
210
- /**
211
- * URL to redirect to for authentication.
212
- */
213
- redirectTo;
214
- constructor(message = "Unauthenticated.", guard, redirectTo) {
215
- super(401, message);
216
- this.name = "AuthenticationException";
217
- this.guard = guard;
218
- this.redirectTo = redirectTo;
219
- }
220
- /**
221
- * Create exception with redirect URL.
222
- */
223
- static withRedirect(redirectTo, message) {
224
- return new _AuthenticationException(message, void 0, redirectTo);
225
- }
226
- /**
227
- * Create exception for a specific guard.
228
- */
229
- static forGuard(guard, message) {
230
- return new _AuthenticationException(message, guard);
231
- }
232
- };
233
-
234
- // src/auth/RequestAuthContext.ts
235
- var RequestAuthContext = class {
236
- constructor(manager, ctx, currentSession, resolveGuard) {
237
- this.manager = manager;
238
- this.ctx = ctx;
239
- this.currentSession = currentSession;
240
- this.resolveGuard = resolveGuard;
241
- }
242
- guardCache = /* @__PURE__ */ new Map();
243
- guard(name) {
244
- const key = name ?? this.manager.getDefaultGuard();
245
- if (!this.guardCache.has(key)) {
246
- const guard = this.resolveGuard(name);
247
- this.guardCache.set(key, guard);
248
- }
249
- return this.guardCache.get(key);
250
- }
251
- session() {
252
- return this.currentSession;
253
- }
254
- async check() {
255
- return this.guard().check();
256
- }
257
- async guest() {
258
- return this.guard().guest();
259
- }
260
- async user() {
261
- return this.guard().user();
262
- }
263
- async userOrFail() {
264
- const u = await this.user();
265
- if (!u) throw new AuthenticationException();
266
- return u;
267
- }
268
- async id() {
269
- return this.guard().id();
270
- }
271
- async login(user, remember) {
272
- await this.guard().login(user, remember);
273
- }
274
- async attempt(credentials, remember) {
275
- return this.guard().attempt(credentials, remember);
276
- }
277
- async logout() {
278
- await this.guard().logout();
279
- }
280
- };
281
-
282
- // src/auth/password/ScryptHasher.ts
283
- var ScryptHasher = class {
284
- algorithm;
285
- memoryCost;
286
- timeCost;
287
- cost;
288
- constructor(options = {}) {
289
- this.algorithm = options.algorithm ?? "argon2id";
290
- this.memoryCost = options.memoryCost;
291
- this.timeCost = options.timeCost;
292
- this.cost = options.cost;
293
- }
294
- async hash(plain) {
295
- if (this.algorithm === "bcrypt") {
296
- return Bun.password.hash(plain, {
297
- algorithm: "bcrypt",
298
- cost: this.cost
299
- });
300
- }
301
- return Bun.password.hash(plain, {
302
- algorithm: this.algorithm,
303
- memoryCost: this.memoryCost,
304
- timeCost: this.timeCost
305
- });
306
- }
307
- async verify(hashed, plain) {
308
- return Bun.password.verify(plain, hashed);
309
- }
310
- needsRehash(hashed) {
311
- if (this.algorithm === "bcrypt") {
312
- if (!hashed.startsWith("$2")) {
313
- return true;
314
- }
315
- if (this.cost != null) {
316
- const costSegment = hashed.slice(4, 6);
317
- const parsedCost = Number.parseInt(costSegment, 10);
318
- if (!Number.isNaN(parsedCost) && parsedCost !== this.cost) {
319
- return true;
320
- }
321
- }
322
- return false;
323
- }
324
- if (!hashed.startsWith(`$${this.algorithm}$`)) {
325
- return true;
326
- }
327
- const [, , , parameterSegment] = hashed.split("$");
328
- if (!parameterSegment) {
329
- return false;
330
- }
331
- const params = Object.fromEntries(
332
- parameterSegment.split(",").map((pair) => pair.split("=")).filter((parts) => parts.length === 2)
333
- );
334
- if (this.memoryCost != null) {
335
- const memory = Number(params.m);
336
- if (!Number.isNaN(memory) && memory !== this.memoryCost) {
337
- return true;
338
- }
339
- }
340
- if (this.timeCost != null) {
341
- const time = Number(params.t);
342
- if (!Number.isNaN(time) && time !== this.timeCost) {
343
- return true;
344
- }
345
- }
346
- return false;
347
- }
348
- };
349
-
350
- // src/auth/providers/UserProvider.ts
351
- var BaseUserProvider = class {
352
- async setRememberToken(user, token) {
353
- if (typeof user.setRememberToken === "function") {
354
- await user.setRememberToken(token);
355
- }
356
- }
357
- async getRememberToken(user) {
358
- if (typeof user.getRememberToken === "function") {
359
- return await user.getRememberToken() ?? null;
360
- }
361
- return null;
362
- }
363
- };
364
-
365
- // src/auth/providers/ModelUserProvider.ts
366
- var ModelUserProvider = class extends BaseUserProvider {
367
- constructor(model, options = {}) {
368
- super();
369
- this.model = model;
370
- this.idColumn = options.idColumn ?? "id";
371
- this.usernameColumn = options.usernameColumn ?? "email";
372
- this.passwordColumn = options.passwordColumn ?? "password";
373
- this.rememberTokenColumn = options.rememberTokenColumn ?? "remember_token";
374
- this.hasher = options.hasher ?? new ScryptHasher();
375
- this.credentialsPasswordField = options.credentialsPasswordField ?? "password";
376
- }
377
- idColumn;
378
- usernameColumn;
379
- passwordColumn;
380
- rememberTokenColumn;
381
- hasher;
382
- credentialsPasswordField;
383
- cast(record) {
384
- if (record && typeof record === "object") {
385
- return record;
386
- }
387
- return null;
388
- }
389
- async retrieveById(identifier) {
390
- const record = await this.model.find(identifier, this.idColumn);
391
- return this.cast(record);
392
- }
393
- async retrieveByCredentials(credentials) {
394
- const rememberToken = credentials["rememberToken"] ?? credentials["remember_token"];
395
- if (rememberToken != null) {
396
- const records2 = await this.model.where({ [this.rememberTokenColumn]: rememberToken });
397
- return this.cast(records2[0] ?? null);
398
- }
399
- const username = credentials[this.usernameColumn];
400
- if (username == null) {
401
- return null;
402
- }
403
- const records = await this.model.where({ [this.usernameColumn]: username });
404
- return this.cast(records[0] ?? null);
405
- }
406
- async validateCredentials(user, credentials) {
407
- const plain = credentials[this.credentialsPasswordField];
408
- if (typeof plain !== "string") {
409
- return false;
410
- }
411
- const hashed = user[this.passwordColumn];
412
- if (typeof hashed !== "string") {
413
- await this.hasher.hash("dummy-timing-equalization");
414
- return false;
415
- }
416
- return this.hasher.verify(hashed, plain);
417
- }
418
- getId(user) {
419
- return user[this.idColumn];
420
- }
421
- async setRememberToken(user, token) {
422
- if (typeof user[this.rememberTokenColumn] !== "undefined") {
423
- ;
424
- user[this.rememberTokenColumn] = token;
425
- await this.model.update({ [this.idColumn]: this.getId(user) }, { [this.rememberTokenColumn]: token });
426
- }
427
- }
428
- async getRememberToken(user) {
429
- const token = user[this.rememberTokenColumn];
430
- if (token == null) {
431
- return null;
432
- }
433
- return String(token);
434
- }
435
- };
436
-
437
- // src/auth/utils.ts
438
- import { randomBytes as randomBytes2, createHash, timingSafeEqual as timingSafeEqual2 } from "crypto";
439
- function hashToken(token, algorithm = "sha256") {
440
- return createHash(algorithm).update(token).digest("hex");
441
- }
442
- function generateToken(length = 32) {
443
- return randomBytes2(length).toString("hex");
444
- }
445
- function generateId() {
446
- return randomBytes2(16).toString("hex");
447
- }
448
- function secureCompare(a, b) {
449
- if (a.length !== b.length) return false;
450
- try {
451
- return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
452
- } catch {
453
- return false;
454
- }
455
- }
456
- function secureStringCompare(a, b) {
457
- const bufA = Buffer.from(a, "utf8");
458
- const bufB = Buffer.from(b, "utf8");
459
- if (bufA.length !== bufB.length) return false;
460
- return timingSafeEqual2(bufA, bufB);
461
- }
462
- function buildTokenUrl(baseUrl, token, email) {
463
- const url = new URL(baseUrl);
464
- url.searchParams.set("token", token);
465
- if (email) {
466
- url.searchParams.set("email", email);
467
- }
468
- return url.toString();
469
- }
470
- function parseTokenUrl(url) {
471
- try {
472
- const parsed = new URL(url);
473
- return {
474
- token: parsed.searchParams.get("token"),
475
- email: parsed.searchParams.get("email")
476
- };
477
- } catch {
478
- return { token: null, email: null };
479
- }
480
- }
481
-
482
- // src/auth/SessionGuard.ts
483
- var DEFAULT_SESSION_KEY = "auth:user_id";
484
- var DEFAULT_REMEMBER_KEY = "auth:remember_token";
485
- var SessionGuard = class {
486
- constructor(options) {
487
- this.options = options;
488
- }
489
- cachedUser;
490
- get currentSession() {
491
- return this.options.session;
492
- }
493
- get provider() {
494
- return this.options.provider;
495
- }
496
- sessionKey() {
497
- return this.options.sessionKey ?? DEFAULT_SESSION_KEY;
498
- }
499
- rememberSessionKey() {
500
- return this.options.rememberSessionKey ?? DEFAULT_REMEMBER_KEY;
501
- }
502
- async loadRememberedUser() {
503
- if (!this.currentSession || !this.provider.getRememberToken) {
504
- return null;
505
- }
506
- const rememberToken = this.currentSession.get(this.rememberSessionKey());
507
- if (!rememberToken) {
508
- return null;
509
- }
510
- const user = await this.provider.retrieveByCredentials({ rememberToken });
511
- if (!user) {
512
- this.currentSession.forget(this.rememberSessionKey());
513
- return null;
514
- }
515
- const providerToken = await this.provider.getRememberToken?.(user);
516
- if (!providerToken || !secureStringCompare(providerToken, rememberToken)) {
517
- this.currentSession.forget(this.rememberSessionKey());
518
- return null;
519
- }
520
- this.cachedUser = user;
521
- await this.provider.setRememberToken?.(user, rememberToken);
522
- return user;
523
- }
524
- async resolveUser() {
525
- if (this.cachedUser !== void 0) {
526
- return this.cachedUser;
527
- }
528
- const session = this.currentSession;
529
- if (!session) {
530
- this.cachedUser = null;
531
- return null;
532
- }
533
- const identifier = session.get(this.sessionKey());
534
- if (identifier == null) {
535
- const remembered = await this.loadRememberedUser();
536
- this.cachedUser = remembered;
537
- return remembered;
538
- }
539
- const user = await this.provider.retrieveById(identifier);
540
- if (!user) {
541
- session.forget(this.sessionKey());
542
- this.cachedUser = null;
543
- return null;
544
- }
545
- this.cachedUser = user;
546
- return user;
547
- }
548
- async check() {
549
- return await this.resolveUser() !== null;
550
- }
551
- async guest() {
552
- return !await this.check();
553
- }
554
- async user() {
555
- const user = await this.resolveUser();
556
- return user ?? null;
557
- }
558
- async id() {
559
- const session = this.currentSession;
560
- if (!session) {
561
- return null;
562
- }
563
- return session.get(this.sessionKey()) ?? null;
564
- }
565
- async remember(user) {
566
- if (!this.currentSession || !this.provider.setRememberToken) {
567
- return;
568
- }
569
- const token = generateToken(32);
570
- await this.provider.setRememberToken?.(user, token);
571
- this.currentSession.set(this.rememberSessionKey(), token);
572
- }
573
- async login(user, remember = false) {
574
- const castUser = user;
575
- const session = this.currentSession;
576
- if (!session) {
577
- throw new Error("SessionGuard: session middleware is required to use the session guard.");
578
- }
579
- session.regenerate();
580
- const identifier = this.provider.getId(castUser);
581
- session.set(this.sessionKey(), identifier);
582
- this.cachedUser = castUser;
583
- if (remember) {
584
- await this.remember(castUser);
585
- } else {
586
- session.forget(this.rememberSessionKey());
587
- }
588
- }
589
- async logout() {
590
- const session = this.currentSession;
591
- if (!session) {
592
- return;
593
- }
594
- session.forget(this.sessionKey());
595
- session.forget(this.rememberSessionKey());
596
- this.cachedUser = null;
597
- }
598
- async attempt(credentials, remember = false) {
599
- const user = await this.validate(credentials);
600
- if (!user) {
601
- return false;
602
- }
603
- await this.login(user, remember);
604
- return true;
605
- }
606
- async validate(credentials) {
607
- const user = await this.provider.retrieveByCredentials(credentials);
608
- if (!user) {
609
- await new Promise((resolve) => setTimeout(resolve, 100));
610
- return null;
611
- }
612
- const valid = await this.provider.validateCredentials(user, credentials);
613
- if (!valid) {
614
- return null;
615
- }
616
- return user;
617
- }
618
- session() {
619
- return this.currentSession;
620
- }
621
- };
622
-
623
- // src/auth/AuthManager.ts
624
- var DEFAULT_GUARD = "web";
625
- var AuthManager = class {
626
- guards = /* @__PURE__ */ new Map();
627
- providers = /* @__PURE__ */ new Map();
628
- defaultGuard;
629
- constructor(options = {}) {
630
- this.defaultGuard = options.defaultGuard ?? DEFAULT_GUARD;
631
- }
632
- registerGuard(name, factory) {
633
- this.guards.set(name, { factory });
634
- }
635
- registerProvider(name, factory) {
636
- this.providers.set(name, { factory });
637
- }
638
- getProvider(name) {
639
- const entry = this.providers.get(name);
640
- if (!entry) {
641
- throw new Error(`AuthManager: provider "${name}" has not been registered.`);
642
- }
643
- if (!entry.instance) {
644
- const instance = entry.factory(this);
645
- entry.instance = instance;
646
- this.providers.set(name, entry);
647
- }
648
- return entry.instance;
649
- }
650
- createGuard(name, context) {
651
- const entry = this.guards.get(name);
652
- if (!entry) {
653
- throw new Error(`AuthManager: guard "${name}" has not been registered.`);
654
- }
655
- return entry.factory(context);
656
- }
657
- guardNames() {
658
- return Array.from(this.guards.keys());
659
- }
660
- setDefaultGuard(name) {
661
- if (!this.guards.has(name)) {
662
- throw new Error(`AuthManager: cannot set default guard to unregistered guard "${name}".`);
663
- }
664
- this.defaultGuard = name;
665
- }
666
- getDefaultGuard() {
667
- return this.defaultGuard;
668
- }
669
- createAuthContext(ctx, options = {}) {
670
- const guardName = options.guard ?? this.defaultGuard;
671
- const session = getSessionFromContext(ctx);
672
- const guardFactory = (name) => {
673
- const targetName = name ?? guardName;
674
- return this.createGuard(targetName, {
675
- ctx,
676
- session,
677
- manager: this
678
- });
679
- };
680
- return new RequestAuthContext(this, ctx, session, guardFactory);
681
- }
682
- async attempt(name, ctx, credentials, remember) {
683
- const guard = this.createAuthContext(ctx, { guard: name }).guard(name);
684
- return guard.attempt(credentials, remember);
685
- }
686
- /**
687
- * Shorthand method to register a model-based authentication provider and session guard.
688
- * This simplifies the common case of authenticating users via a database model.
689
- *
690
- * @param model - The model class to use for user authentication
691
- * @param options - Options for the ModelUserProvider (partial, with defaults)
692
- * @param providerName - Name for the provider (defaults to 'users')
693
- * @param guardName - Name for the guard (defaults to 'web')
694
- */
695
- useModel(model, options = {}, providerName = "users", guardName = "web") {
696
- const defaultOptions = {
697
- usernameColumn: "email",
698
- passwordColumn: "passwordHash",
699
- rememberTokenColumn: "rememberToken",
700
- credentialsPasswordField: "password",
701
- hasher: new ScryptHasher(),
702
- ...options
703
- };
704
- this.registerProvider(providerName, () => new ModelUserProvider(model, defaultOptions));
705
- this.registerGuard(guardName, ({ session, manager }) => {
706
- const provider = manager.getProvider(providerName);
707
- return new SessionGuard({ provider, session });
708
- });
709
- this.setDefaultGuard(guardName);
710
- }
711
- };
712
-
713
- // src/encryption/Hash.ts
714
1
  import {
715
- createHash as createHash2,
716
- createHmac as createHmac2,
717
- randomBytes as randomBytes3,
718
- scrypt,
719
- timingSafeEqual as timingSafeEqual3
720
- } from "crypto";
721
- import { promisify } from "util";
722
- var scryptAsync = promisify(scrypt);
723
- async function hashPassword(password, options = {}) {
724
- const {
725
- cost = 16384,
726
- // N
727
- memory = 8,
728
- // r (block size)
729
- saltLength = 16,
730
- keyLength = 64
731
- } = options;
732
- const salt = randomBytes3(saltLength);
733
- const derived = await scryptAsync(password, salt, keyLength, {
734
- N: cost,
735
- r: memory,
736
- p: 1
737
- });
738
- const params = `N=${cost},r=${memory},p=1`;
739
- return `$scrypt$${params}$${salt.toString("base64")}$${derived.toString("base64")}`;
740
- }
741
- async function verifyPassword(password, hash) {
742
- if (!hash.startsWith("$scrypt$")) {
743
- throw new Error("Invalid password hash format.");
744
- }
745
- const parts = hash.split("$");
746
- if (parts.length !== 5) {
747
- throw new Error("Invalid password hash format.");
748
- }
749
- const [, , paramsStr, saltB64, hashB64] = parts;
750
- const params = {};
751
- for (const param of paramsStr.split(",")) {
752
- const [key, value] = param.split("=");
753
- params[key] = parseInt(value, 10);
754
- }
755
- const salt = Buffer.from(saltB64, "base64");
756
- const expectedHash = Buffer.from(hashB64, "base64");
757
- const derived = await scryptAsync(password, salt, expectedHash.length, {
758
- N: params.N,
759
- r: params.r,
760
- p: params.p ?? 1
761
- });
762
- return timingSafeEqual3(derived, expectedHash);
763
- }
764
- function needsRehash(hash, options = {}) {
765
- const { cost = 16384, memory = 8 } = options;
766
- if (!hash.startsWith("$scrypt$")) {
767
- return true;
768
- }
769
- const parts = hash.split("$");
770
- if (parts.length !== 5) {
771
- return true;
772
- }
773
- const paramsStr = parts[2];
774
- const params = {};
775
- for (const param of paramsStr.split(",")) {
776
- const [key, value] = param.split("=");
777
- params[key] = parseInt(value, 10);
778
- }
779
- return params.N !== cost || params.r !== memory;
780
- }
781
-
782
- // src/auth/password/NodeHasher.ts
783
- var NodeHasher = class {
784
- options;
785
- constructor(options = {}) {
786
- this.options = {
787
- cost: options.cost,
788
- memory: options.memory,
789
- saltLength: options.saltLength,
790
- keyLength: options.keyLength
791
- };
792
- }
793
- async hash(plain) {
794
- return hashPassword(plain, this.options);
795
- }
796
- async verify(hashed, plain) {
797
- return verifyPassword(plain, hashed);
798
- }
799
- needsRehash(hashed) {
800
- return needsRehash(hashed, this.options);
801
- }
802
- };
803
-
804
- // src/auth/AuthenticatableModel.ts
805
- import { Model } from "@guren/orm";
806
- var AuthenticatableModel = class extends Model {
807
- static createType = void 0;
808
- static passwordField = "password";
809
- static passwordHashField = "passwordHash";
810
- static passwordHasher = null;
811
- static resolvePasswordField() {
812
- return this.passwordField ?? "password";
813
- }
814
- static resolvePasswordHashField() {
815
- return this.passwordHashField ?? "passwordHash";
816
- }
817
- static resolvePasswordHasher() {
818
- if (this.passwordHasher) {
819
- return this.passwordHasher;
820
- }
821
- const hasher = new ScryptHasher();
822
- this.passwordHasher = hasher;
823
- return hasher;
824
- }
825
- static async preparePersistencePayload(data) {
826
- const basePayload = await super.preparePersistencePayload(data);
827
- const passwordField = this.resolvePasswordField();
828
- if (!(passwordField in basePayload)) {
829
- return basePayload;
830
- }
831
- const payload = { ...basePayload };
832
- const plainPassword = payload[passwordField];
833
- const hashField = this.resolvePasswordHashField();
834
- if (typeof plainPassword === "string" && plainPassword.length > 0) {
835
- const hasher = this.resolvePasswordHasher();
836
- payload[hashField] = await hasher.hash(plainPassword);
837
- }
838
- if (passwordField !== hashField) {
839
- delete payload[passwordField];
840
- }
841
- return payload;
842
- }
843
- };
844
-
845
- // src/encryption/MessageSigner.ts
846
- import { createHmac as createHmac3, timingSafeEqual as timingSafeEqual4 } from "crypto";
847
- function encodeBase64Url(value) {
848
- return Buffer.from(value).toString("base64url");
849
- }
850
- function decodeBase64Url(value) {
851
- return Buffer.from(value, "base64url").toString("utf8");
852
- }
853
- function createSignature(input, key) {
854
- return encodeBase64Url(createHmac3("sha256", key).update(input).digest());
855
- }
856
- function signaturesMatch(actual, expected) {
857
- const actualBuffer = Buffer.from(actual, "utf8");
858
- const expectedBuffer = Buffer.from(expected, "utf8");
859
- if (actualBuffer.length !== expectedBuffer.length) {
860
- return false;
861
- }
862
- return timingSafeEqual4(actualBuffer, expectedBuffer);
863
- }
864
- var MessageSigner = class {
865
- constructor(keyring) {
866
- this.keyring = keyring;
867
- }
868
- sign(payload, options = {}) {
869
- const now = Date.now();
870
- const claims = {
871
- ...payload,
872
- iat: Math.floor(now / 1e3),
873
- ...options.purpose ? { purpose: options.purpose } : {},
874
- ...typeof options.expiresIn === "number" ? { exp: Math.floor((now + options.expiresIn) / 1e3) } : {}
875
- };
876
- const encodedPayload = encodeBase64Url(JSON.stringify(claims));
877
- const signature = createSignature(encodedPayload, this.keyring.current);
878
- return `${encodedPayload}.${signature}`;
879
- }
880
- verify(token, options = {}) {
881
- const [encodedPayload, signature, extra] = token.split(".");
882
- if (!encodedPayload || !signature || extra) {
883
- return null;
884
- }
885
- const verified = [this.keyring.current, ...this.keyring.previous].some(
886
- (key) => signaturesMatch(signature, createSignature(encodedPayload, key))
887
- );
888
- if (!verified) {
889
- return null;
890
- }
891
- let payload;
892
- try {
893
- payload = JSON.parse(decodeBase64Url(encodedPayload));
894
- } catch {
895
- return null;
896
- }
897
- if (options.purpose && payload.purpose !== options.purpose) {
898
- return null;
899
- }
900
- if (typeof payload.exp === "number" && Math.floor(Date.now() / 1e3) > payload.exp && options.allowExpired !== true) {
901
- return null;
902
- }
903
- return payload;
904
- }
905
- };
906
-
907
- // src/auth/password-reset.ts
908
- var MemoryPasswordResetStore = class {
909
- tokens = /* @__PURE__ */ new Map();
910
- async store(tokenId, email, expiresAt) {
911
- this.tokens.set(tokenId, { email, expiresAt });
912
- }
913
- async find(tokenId) {
914
- const record = this.tokens.get(tokenId);
915
- if (!record) return null;
916
- if (record.expiresAt < /* @__PURE__ */ new Date()) {
917
- this.tokens.delete(tokenId);
918
- return null;
919
- }
920
- return record;
921
- }
922
- async delete(tokenId) {
923
- this.tokens.delete(tokenId);
924
- }
925
- async deleteForEmail(email) {
926
- for (const [hash, record] of this.tokens) {
927
- if (record.email === email) {
928
- this.tokens.delete(hash);
929
- }
930
- }
931
- }
932
- /** Clear all tokens (useful for testing) */
933
- clear() {
934
- this.tokens.clear();
935
- }
936
- };
937
- var DEFAULT_EXPIRES_IN = 60 * 60 * 1e3;
938
- var DEFAULT_TOKEN_LENGTH = 32;
939
- var PASSWORD_RESET_PURPOSE = "password-reset";
940
- function createPasswordResetSigner() {
941
- return new MessageSigner(deriveAppKeyring(getAppKeyringFromEnv(), "password-reset-signing"));
942
- }
943
- async function createPasswordResetToken(email, store, config = {}) {
944
- const expiresIn = config.expiresIn ?? DEFAULT_EXPIRES_IN;
945
- const tokenLength = config.tokenLength ?? DEFAULT_TOKEN_LENGTH;
946
- await store.deleteForEmail(email);
947
- const tokenId = generateId();
948
- const expiresAt = new Date(Date.now() + expiresIn);
949
- const signer = createPasswordResetSigner();
950
- const token = signer.sign(
951
- {
952
- id: tokenId,
953
- email: email.toLowerCase(),
954
- bytes: tokenLength
955
- },
956
- {
957
- purpose: PASSWORD_RESET_PURPOSE,
958
- expiresIn
959
- }
960
- );
961
- await store.store(tokenId, email.toLowerCase(), expiresAt);
962
- return { token, tokenId, expiresAt };
963
- }
964
- async function verifyPasswordResetToken(token, store, _config = {}) {
965
- const signer = createPasswordResetSigner();
966
- const payload = signer.verify(token, {
967
- purpose: PASSWORD_RESET_PURPOSE,
968
- allowExpired: true
969
- });
970
- if (!payload?.id || !payload.email) {
971
- return null;
972
- }
973
- if (typeof payload.exp === "number" && payload.exp < Math.floor(Date.now() / 1e3)) {
974
- await store.delete(payload.id);
975
- return null;
976
- }
977
- const record = await store.find(payload.id);
978
- if (!record) return null;
979
- return record.email.toLowerCase() === payload.email.toLowerCase() ? record.email : null;
980
- }
981
- async function completePasswordReset(token, newPassword, store, provider, updatePassword, _config = {}) {
982
- const signer = createPasswordResetSigner();
983
- const payload = signer.verify(token, {
984
- purpose: PASSWORD_RESET_PURPOSE,
985
- allowExpired: true
986
- });
987
- if (!payload?.id || !payload.email) {
988
- return null;
989
- }
990
- if (typeof payload.exp === "number" && payload.exp < Math.floor(Date.now() / 1e3)) {
991
- await store.delete(payload.id);
992
- return null;
993
- }
994
- const record = await store.find(payload.id);
995
- if (!record) return null;
996
- if (record.email.toLowerCase() !== payload.email.toLowerCase()) {
997
- return null;
998
- }
999
- const user = await provider.retrieveByCredentials({ email: record.email });
1000
- if (!user) {
1001
- await store.delete(payload.id);
1002
- return null;
1003
- }
1004
- await updatePassword(user, newPassword);
1005
- await store.delete(payload.id);
1006
- return user;
1007
- }
1008
- var buildPasswordResetUrl = buildTokenUrl;
1009
- var parsePasswordResetUrl = parseTokenUrl;
1010
-
1011
- // src/auth/email-verification.ts
1012
- var MemoryEmailVerificationStore = class {
1013
- tokens = /* @__PURE__ */ new Map();
1014
- async store(token) {
1015
- this.tokens.set(token.tokenId, token);
1016
- }
1017
- async findByTokenId(tokenId) {
1018
- return this.tokens.get(tokenId) ?? null;
1019
- }
1020
- async delete(tokenId) {
1021
- this.tokens.delete(tokenId);
1022
- }
1023
- async deleteForEmail(email) {
1024
- const normalizedEmail = email.toLowerCase();
1025
- for (const [key, token] of this.tokens.entries()) {
1026
- if (token.email.toLowerCase() === normalizedEmail) {
1027
- this.tokens.delete(key);
1028
- }
1029
- }
1030
- }
1031
- /**
1032
- * Clear all tokens (useful for testing).
1033
- */
1034
- clear() {
1035
- this.tokens.clear();
1036
- }
1037
- /**
1038
- * Get the count of stored tokens (useful for testing).
1039
- */
1040
- get size() {
1041
- return this.tokens.size;
1042
- }
1043
- };
1044
- var DEFAULT_CONFIG = {
1045
- expiresIn: 24 * 60 * 60 * 1e3,
1046
- // 24 hours
1047
- tokenLength: 32
1048
- };
1049
- var EMAIL_VERIFICATION_PURPOSE = "email-verification";
1050
- function createEmailVerificationSigner() {
1051
- return new MessageSigner(deriveAppKeyring(getAppKeyringFromEnv(), "email-verification-signing"));
1052
- }
1053
- async function createEmailVerificationToken(email, store, config = {}) {
1054
- const { expiresIn, tokenLength } = { ...DEFAULT_CONFIG, ...config };
1055
- await store.deleteForEmail(email);
1056
- const tokenId = generateId();
1057
- const now = /* @__PURE__ */ new Date();
1058
- const expiresAt = new Date(now.getTime() + expiresIn);
1059
- const signer = createEmailVerificationSigner();
1060
- const token = signer.sign(
1061
- {
1062
- id: tokenId,
1063
- email: email.toLowerCase(),
1064
- bytes: tokenLength
1065
- },
1066
- {
1067
- purpose: EMAIL_VERIFICATION_PURPOSE,
1068
- expiresIn
1069
- }
1070
- );
1071
- await store.store({
1072
- email: email.toLowerCase(),
1073
- tokenId,
1074
- expiresAt,
1075
- createdAt: now
1076
- });
1077
- return { token, expiresAt };
1078
- }
1079
- async function verifyEmailToken(token, store, _config = {}) {
1080
- const signer = createEmailVerificationSigner();
1081
- const payload = signer.verify(token, {
1082
- purpose: EMAIL_VERIFICATION_PURPOSE,
1083
- allowExpired: true
1084
- });
1085
- if (!payload?.id || !payload.email) {
1086
- return null;
1087
- }
1088
- if (typeof payload.exp === "number" && payload.exp < Math.floor(Date.now() / 1e3)) {
1089
- await store.delete(payload.id);
1090
- return null;
1091
- }
1092
- const storedToken = await store.findByTokenId(payload.id);
1093
- if (!storedToken) {
1094
- return null;
1095
- }
1096
- if (/* @__PURE__ */ new Date() > storedToken.expiresAt) {
1097
- await store.delete(payload.id);
1098
- return null;
1099
- }
1100
- return storedToken.email.toLowerCase() === payload.email.toLowerCase() ? storedToken.email : null;
1101
- }
1102
- async function completeEmailVerification(token, store, markVerified) {
1103
- const signer = createEmailVerificationSigner();
1104
- const payload = signer.verify(token, {
1105
- purpose: EMAIL_VERIFICATION_PURPOSE,
1106
- allowExpired: true
1107
- });
1108
- if (!payload?.id || !payload.email) {
1109
- return null;
1110
- }
1111
- if (typeof payload.exp === "number" && payload.exp < Math.floor(Date.now() / 1e3)) {
1112
- await store.delete(payload.id);
1113
- return null;
1114
- }
1115
- const storedToken = await store.findByTokenId(payload.id);
1116
- if (!storedToken) {
1117
- return null;
1118
- }
1119
- if (/* @__PURE__ */ new Date() > storedToken.expiresAt) {
1120
- await store.delete(payload.id);
1121
- return null;
1122
- }
1123
- if (storedToken.email.toLowerCase() !== payload.email.toLowerCase()) {
1124
- return null;
1125
- }
1126
- const result = await markVerified(storedToken.email);
1127
- await store.delete(payload.id);
1128
- return result;
1129
- }
1130
- var buildVerificationUrl = buildTokenUrl;
1131
- var parseVerificationUrl = parseTokenUrl;
1132
- function isEmailVerified(user) {
1133
- return user?.emailVerifiedAt != null;
1134
- }
1135
- function requireVerifiedEmail(options = {}) {
1136
- const { redirectTo = "/verify-email" } = options;
1137
- return async (ctx, next) => {
1138
- const getUser = options.getUser ?? (async (c) => {
1139
- const auth = c.get("guren:auth");
1140
- return auth?.user?.() ?? null;
1141
- });
1142
- const user = await getUser(ctx);
1143
- if (!isEmailVerified(user)) {
1144
- return ctx.redirect(redirectTo);
1145
- }
1146
- await next();
1147
- };
1148
- }
1149
-
1150
- // src/auth/api-token.ts
1151
- var MemoryApiTokenStore = class {
1152
- tokens = /* @__PURE__ */ new Map();
1153
- byHash = /* @__PURE__ */ new Map();
1154
- // hash -> id
1155
- async store(token) {
1156
- this.tokens.set(token.id, token);
1157
- this.byHash.set(token.hashedToken, token.id);
1158
- }
1159
- async findByHashedToken(hashedToken) {
1160
- const id = this.byHash.get(hashedToken);
1161
- if (!id) return null;
1162
- return this.tokens.get(id) ?? null;
1163
- }
1164
- async findByUserId(userId) {
1165
- const userIdStr = String(userId);
1166
- return Array.from(this.tokens.values()).filter(
1167
- (t) => String(t.userId) === userIdStr
1168
- );
1169
- }
1170
- async delete(id) {
1171
- const token = this.tokens.get(id);
1172
- if (token) {
1173
- this.byHash.delete(token.hashedToken);
1174
- this.tokens.delete(id);
1175
- }
1176
- }
1177
- async deleteForUser(userId) {
1178
- const userIdStr = String(userId);
1179
- for (const [id, token] of this.tokens.entries()) {
1180
- if (String(token.userId) === userIdStr) {
1181
- this.byHash.delete(token.hashedToken);
1182
- this.tokens.delete(id);
1183
- }
1184
- }
1185
- }
1186
- async updateLastUsed(id, timestamp) {
1187
- const token = this.tokens.get(id);
1188
- if (token) {
1189
- token.lastUsedAt = timestamp;
1190
- }
1191
- }
1192
- /**
1193
- * Clear all tokens (useful for testing).
1194
- */
1195
- clear() {
1196
- this.tokens.clear();
1197
- this.byHash.clear();
1198
- }
1199
- /**
1200
- * Get the number of stored tokens.
1201
- */
1202
- get size() {
1203
- return this.tokens.size;
1204
- }
1205
- };
1206
- async function createApiToken(store, options) {
1207
- const {
1208
- name,
1209
- userId,
1210
- abilities = ["*"],
1211
- expiresIn = null,
1212
- tokenLength = 32
1213
- } = options;
1214
- const id = generateId();
1215
- const plainToken = generateToken(tokenLength);
1216
- const hashedToken = hashToken(plainToken);
1217
- const now = /* @__PURE__ */ new Date();
1218
- const token = {
1219
- id,
1220
- name,
1221
- hashedToken,
1222
- userId,
1223
- abilities,
1224
- lastUsedAt: null,
1225
- expiresAt: expiresIn == null ? null : new Date(now.getTime() + expiresIn),
1226
- createdAt: now
1227
- };
1228
- await store.store(token);
1229
- return {
1230
- plainTextToken: `${id}|${plainToken}`,
1231
- token
1232
- };
1233
- }
1234
- function parseApiToken(plainTextToken) {
1235
- const parts = plainTextToken.split("|");
1236
- if (parts.length !== 2) return null;
1237
- const [id, token] = parts;
1238
- if (!id || !token) return null;
1239
- return { id, token };
1240
- }
1241
- async function verifyApiToken(plainTextToken, store, options = {}) {
1242
- const { updateLastUsed = true } = options;
1243
- const parsed = parseApiToken(plainTextToken);
1244
- if (!parsed) return null;
1245
- const hashedToken = hashToken(parsed.token);
1246
- const token = await store.findByHashedToken(hashedToken);
1247
- if (!token) return null;
1248
- if (token.id !== parsed.id) return null;
1249
- if (token.expiresAt && /* @__PURE__ */ new Date() > token.expiresAt) {
1250
- return null;
1251
- }
1252
- if (!secureCompare(token.hashedToken, hashedToken)) {
1253
- return null;
1254
- }
1255
- if (updateLastUsed) {
1256
- await store.updateLastUsed(token.id, /* @__PURE__ */ new Date());
1257
- }
1258
- return {
1259
- token,
1260
- userId: token.userId,
1261
- abilities: token.abilities
1262
- };
1263
- }
1264
- function tokenCan(token, ability) {
1265
- if (token.abilities.includes("*")) return true;
1266
- return token.abilities.includes(ability);
1267
- }
1268
- function tokenCanAll(token, abilities) {
1269
- if (token.abilities.includes("*")) return true;
1270
- return abilities.every((ability) => token.abilities.includes(ability));
1271
- }
1272
- function tokenCanAny(token, abilities) {
1273
- if (token.abilities.includes("*")) return true;
1274
- return abilities.some((ability) => token.abilities.includes(ability));
1275
- }
1276
- async function revokeApiToken(id, store) {
1277
- await store.delete(id);
1278
- }
1279
- async function revokeAllApiTokens(userId, store) {
1280
- await store.deleteForUser(userId);
1281
- }
1282
- async function getUserApiTokens(userId, store) {
1283
- return store.findByUserId(userId);
1284
- }
1285
- var API_TOKEN_KEY = "guren:api-token";
1286
- function createBearerTokenMiddleware(options) {
1287
- const {
1288
- store,
1289
- loadUser,
1290
- abilities,
1291
- onUnauthorized,
1292
- onForbidden,
1293
- headerName = "Authorization",
1294
- updateLastUsed = true
1295
- } = options;
1296
- return async (ctx, next) => {
1297
- const authHeader = ctx.req.header(headerName);
1298
- if (!authHeader) {
1299
- if (onUnauthorized) return onUnauthorized(ctx);
1300
- return ctx.json({ error: "Authentication required" }, 401);
1301
- }
1302
- const match = authHeader.match(/^Bearer\s+(.+)$/i);
1303
- if (!match) {
1304
- if (onUnauthorized) return onUnauthorized(ctx);
1305
- return ctx.json({ error: "Invalid authorization format" }, 401);
1306
- }
1307
- const plainTextToken = match[1];
1308
- const result = await verifyApiToken(plainTextToken, store, { updateLastUsed });
1309
- if (!result) {
1310
- if (onUnauthorized) return onUnauthorized(ctx);
1311
- return ctx.json({ error: "Invalid or expired token" }, 401);
1312
- }
1313
- if (abilities && abilities.length > 0) {
1314
- if (!tokenCanAll(result.token, abilities)) {
1315
- if (onForbidden) return onForbidden(ctx, abilities);
1316
- return ctx.json(
1317
- { error: "Token lacks required abilities", required: abilities },
1318
- 403
1319
- );
1320
- }
1321
- }
1322
- ctx.set(API_TOKEN_KEY, result);
1323
- if (loadUser) {
1324
- const user = await loadUser(result.userId);
1325
- ctx.set("guren:user", user);
1326
- }
1327
- return next();
1328
- };
1329
- }
1330
- function getApiToken(ctx) {
1331
- return ctx.get(API_TOKEN_KEY);
1332
- }
1333
- function getApiTokenOrFail(ctx) {
1334
- const result = getApiToken(ctx);
1335
- if (!result) {
1336
- throw new AuthenticationException("Unauthenticated.");
1337
- }
1338
- return result;
1339
- }
1340
-
1341
- // src/auth/oauth/index.ts
1342
- var DEFAULT_STATE_EXPIRES_IN = 10 * 60 * 1e3;
1343
- var DEFAULT_STATE_LENGTH = 24;
1344
- var DEFAULT_STATE_HASH_ALGORITHM = "sha256";
1345
- var MemoryOAuthStateStore = class {
1346
- states = /* @__PURE__ */ new Map();
1347
- async store(stateHash, payload) {
1348
- this.states.set(stateHash, payload);
1349
- }
1350
- async find(stateHash) {
1351
- const payload = this.states.get(stateHash);
1352
- if (!payload) return null;
1353
- if (payload.expiresAt.getTime() <= Date.now()) {
1354
- this.states.delete(stateHash);
1355
- return null;
1356
- }
1357
- return payload;
1358
- }
1359
- async delete(stateHash) {
1360
- this.states.delete(stateHash);
1361
- }
1362
- clear() {
1363
- this.states.clear();
1364
- }
1365
- };
1366
- var OAuthManager = class {
1367
- providers = /* @__PURE__ */ new Map();
1368
- stateStore;
1369
- stateConfig;
1370
- constructor(options = {}) {
1371
- this.stateStore = options.stateStore ?? new MemoryOAuthStateStore();
1372
- this.stateConfig = {
1373
- expiresIn: options.stateConfig?.expiresIn ?? DEFAULT_STATE_EXPIRES_IN,
1374
- stateLength: options.stateConfig?.stateLength ?? DEFAULT_STATE_LENGTH,
1375
- hashAlgorithm: options.stateConfig?.hashAlgorithm ?? DEFAULT_STATE_HASH_ALGORITHM
1376
- };
1377
- }
1378
- registerProvider(name, config) {
1379
- this.providers.set(name, config);
1380
- }
1381
- providerNames() {
1382
- return Array.from(this.providers.keys()).sort((a, b) => a.localeCompare(b));
1383
- }
1384
- getProvider(name) {
1385
- const provider = this.providers.get(name);
1386
- if (!provider) {
1387
- throw new AuthenticationException(`OAuth provider "${name}" is not configured.`);
1388
- }
1389
- return provider;
1390
- }
1391
- async authorize(providerName, options = {}) {
1392
- const provider = this.getProvider(providerName);
1393
- const { state, expiresAt } = await createOAuthState(
1394
- providerName,
1395
- this.stateStore,
1396
- this.stateConfig,
1397
- options.redirectTo,
1398
- options.state
1399
- );
1400
- const scope = options.scope ?? provider.scopes;
1401
- const url = buildOAuthAuthorizeUrl(provider, state, { scope, extraParams: options.extraParams });
1402
- return { url, state, expiresAt };
1403
- }
1404
- async user(providerName, payload) {
1405
- const provider = this.getProvider(providerName);
1406
- const verified = await verifyOAuthState(payload.state, providerName, this.stateStore, this.stateConfig);
1407
- if (!verified) {
1408
- throw new AuthenticationException("Invalid or expired OAuth state.");
1409
- }
1410
- const token = await exchangeOAuthCode(provider, payload.code);
1411
- return fetchOAuthUserProfile(provider, token);
1412
- }
1413
- };
1414
- function createOAuthManager(options = {}) {
1415
- return new OAuthManager(options);
1416
- }
1417
- async function createOAuthState(provider, store, config = {}, redirectTo, fixedState) {
1418
- const state = fixedState ?? generateToken(config.stateLength ?? DEFAULT_STATE_LENGTH);
1419
- const hashAlgorithm = config.hashAlgorithm ?? DEFAULT_STATE_HASH_ALGORITHM;
1420
- const expiresAt = new Date(Date.now() + (config.expiresIn ?? DEFAULT_STATE_EXPIRES_IN));
1421
- const stateHash = hashToken(state, hashAlgorithm);
1422
- await store.store(stateHash, { provider, redirectTo, expiresAt });
1423
- return { state, expiresAt };
1424
- }
1425
- async function verifyOAuthState(state, provider, store, config = {}) {
1426
- const hashAlgorithm = config.hashAlgorithm ?? DEFAULT_STATE_HASH_ALGORITHM;
1427
- const stateHash = hashToken(state, hashAlgorithm);
1428
- const payload = await store.find(stateHash);
1429
- if (!payload) return null;
1430
- await store.delete(stateHash);
1431
- if (payload.provider !== provider) return null;
1432
- return payload;
1433
- }
1434
- function buildOAuthAuthorizeUrl(provider, state, options = {}) {
1435
- const url = new URL(provider.authorizeUrl);
1436
- url.searchParams.set("response_type", "code");
1437
- url.searchParams.set("client_id", provider.clientId);
1438
- url.searchParams.set("redirect_uri", provider.redirectUri);
1439
- url.searchParams.set("state", state);
1440
- const scopes = options.scope ?? provider.scopes;
1441
- if (scopes && scopes.length > 0) {
1442
- url.searchParams.set("scope", scopes.join(" "));
1443
- }
1444
- for (const [key, value] of Object.entries(options.extraParams ?? {})) {
1445
- url.searchParams.set(key, value);
1446
- }
1447
- return url.toString();
1448
- }
1449
- async function exchangeOAuthCode(provider, code) {
1450
- const params = new URLSearchParams({
1451
- grant_type: "authorization_code",
1452
- code,
1453
- redirect_uri: provider.redirectUri,
1454
- client_id: provider.clientId
1455
- });
1456
- const headers = {
1457
- Accept: "application/json",
1458
- "Content-Type": "application/x-www-form-urlencoded"
1459
- };
1460
- if (provider.tokenAuthMethod === "client_secret_basic") {
1461
- const credentials = Buffer.from(`${provider.clientId}:${provider.clientSecret}`).toString("base64");
1462
- headers.Authorization = `Basic ${credentials}`;
1463
- } else {
1464
- params.set("client_secret", provider.clientSecret);
1465
- }
1466
- const response = await fetch(provider.tokenUrl, {
1467
- method: "POST",
1468
- headers,
1469
- body: params.toString()
1470
- });
1471
- if (!response.ok) {
1472
- throw new AuthenticationException(`OAuth token exchange failed (${response.status}).`);
1473
- }
1474
- const raw = await response.json();
1475
- const accessToken = typeof raw.access_token === "string" ? raw.access_token : "";
1476
- if (!accessToken) {
1477
- throw new AuthenticationException("OAuth token exchange did not return an access token.");
1478
- }
1479
- return {
1480
- accessToken,
1481
- tokenType: typeof raw.token_type === "string" ? raw.token_type : void 0,
1482
- refreshToken: typeof raw.refresh_token === "string" ? raw.refresh_token : void 0,
1483
- expiresIn: typeof raw.expires_in === "number" ? raw.expires_in : void 0,
1484
- scope: typeof raw.scope === "string" ? raw.scope : void 0,
1485
- raw
1486
- };
1487
- }
1488
- async function fetchOAuthUserProfile(provider, token) {
1489
- const response = await fetch(provider.userInfoUrl, {
1490
- method: provider.userInfoMethod ?? "GET",
1491
- headers: {
1492
- Accept: "application/json",
1493
- Authorization: `Bearer ${token.accessToken}`,
1494
- "User-Agent": "guren-oauth"
1495
- }
1496
- });
1497
- if (!response.ok) {
1498
- throw new AuthenticationException(`OAuth user fetch failed (${response.status}).`);
1499
- }
1500
- const raw = await response.json();
1501
- if (provider.mapProfile) {
1502
- return provider.mapProfile(raw, token);
1503
- }
1504
- const idCandidate = raw.id ?? raw.sub;
1505
- const id = typeof idCandidate === "string" || typeof idCandidate === "number" ? String(idCandidate) : "";
1506
- if (!id) {
1507
- throw new AuthenticationException("OAuth user profile is missing an identifier.");
1508
- }
1509
- return {
1510
- id,
1511
- email: typeof raw.email === "string" ? raw.email : void 0,
1512
- name: typeof raw.name === "string" ? raw.name : void 0,
1513
- avatar: typeof raw.avatar_url === "string" ? raw.avatar_url : typeof raw.picture === "string" ? raw.picture : typeof raw.avatar === "string" ? raw.avatar : void 0,
1514
- token,
1515
- raw
1516
- };
1517
- }
1518
- function createGitHubOAuthProviderConfig(input) {
1519
- return {
1520
- ...input,
1521
- authorizeUrl: "https://github.com/login/oauth/authorize",
1522
- tokenUrl: "https://github.com/login/oauth/access_token",
1523
- userInfoUrl: "https://api.github.com/user",
1524
- scopes: input.scopes ?? ["read:user", "user:email"]
1525
- };
1526
- }
1527
- function createGoogleOAuthProviderConfig(input) {
1528
- return {
1529
- ...input,
1530
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
1531
- tokenUrl: "https://oauth2.googleapis.com/token",
1532
- userInfoUrl: "https://openidconnect.googleapis.com/v1/userinfo",
1533
- scopes: input.scopes ?? ["openid", "profile", "email"]
1534
- };
1535
- }
1536
- function createDiscordOAuthProviderConfig(input) {
1537
- return {
1538
- ...input,
1539
- authorizeUrl: "https://discord.com/api/oauth2/authorize",
1540
- tokenUrl: "https://discord.com/api/oauth2/token",
1541
- userInfoUrl: "https://discord.com/api/users/@me",
1542
- scopes: input.scopes ?? ["identify", "email"]
1543
- };
1544
- }
1545
- function buildOAuthRedirectUrl(baseUrl, token, email) {
1546
- return buildTokenUrl(baseUrl, token, email);
1547
- }
1548
- function parseOAuthRedirectUrl(url) {
1549
- return parseTokenUrl(url);
1550
- }
2
+ API_TOKEN_KEY,
3
+ AuthManager,
4
+ AuthenticatableModel,
5
+ BaseUserProvider,
6
+ MemoryApiTokenStore,
7
+ MemoryEmailVerificationStore,
8
+ MemoryOAuthStateStore,
9
+ MemoryPasswordResetStore,
10
+ ModelUserProvider,
11
+ NodeHasher,
12
+ OAuthManager,
13
+ ScryptHasher,
14
+ SessionGuard,
15
+ buildOAuthAuthorizeUrl,
16
+ buildOAuthRedirectUrl,
17
+ buildPasswordResetUrl,
18
+ buildTokenUrl,
19
+ buildVerificationUrl,
20
+ completeEmailVerification,
21
+ completePasswordReset,
22
+ createApiToken,
23
+ createBearerTokenMiddleware,
24
+ createDiscordOAuthProviderConfig,
25
+ createEmailVerificationToken,
26
+ createGitHubOAuthProviderConfig,
27
+ createGoogleOAuthProviderConfig,
28
+ createOAuthManager,
29
+ createOAuthState,
30
+ createPasswordResetToken,
31
+ exchangeOAuthCode,
32
+ fetchOAuthUserProfile,
33
+ generateId,
34
+ generateToken,
35
+ getApiToken,
36
+ getApiTokenOrFail,
37
+ getUserApiTokens,
38
+ hashToken,
39
+ isEmailVerified,
40
+ parseApiToken,
41
+ parseOAuthRedirectUrl,
42
+ parsePasswordResetUrl,
43
+ parseTokenUrl,
44
+ parseVerificationUrl,
45
+ requireVerifiedEmail,
46
+ revokeAllApiTokens,
47
+ revokeApiToken,
48
+ secureCompare,
49
+ secureStringCompare,
50
+ tokenCan,
51
+ tokenCanAll,
52
+ tokenCanAny,
53
+ verifyApiToken,
54
+ verifyEmailToken,
55
+ verifyOAuthState,
56
+ verifyPasswordResetToken
57
+ } from "../chunk-BJXAHGDR.js";
58
+ import "../chunk-DAQKYKLH.js";
59
+ import "../chunk-ONSDE37A.js";
60
+ import "../chunk-QQKTH5KX.js";
1551
61
  export {
1552
62
  API_TOKEN_KEY,
1553
63
  AuthManager,