@adonisjs/auth 10.0.0 → 10.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.
@@ -1,29 +1,129 @@
1
- import { n as E_UNAUTHORIZED_ACCESS } from "../../errors-sGy-K8pd.js";
2
- import "../../symbols-BQLDWwuQ.js";
1
+ import { n as E_UNAUTHORIZED_ACCESS } from "../../errors-eDV8ejO0.js";
2
+ import "../../symbols-C5QEqFvJ.js";
3
3
  import { RuntimeException } from "@adonisjs/core/exceptions";
4
4
  import { inspect } from "node:util";
5
5
  import { createHash } from "node:crypto";
6
6
  import string from "@adonisjs/core/helpers/string";
7
7
  import { Secret, base64, safeEqual } from "@adonisjs/core/helpers";
8
+ //#region modules/session_guard/guard.ts
9
+ /**
10
+ * Session guard uses AdonisJS session store to track logged-in
11
+ * user information.
12
+ *
13
+ * @template UseRememberTokens - Whether the guard supports remember me tokens
14
+ * @template UserProvider - The user provider contract
15
+ *
16
+ * @example
17
+ * const guard = new SessionGuard(
18
+ * 'web',
19
+ * ctx,
20
+ * { useRememberMeTokens: true },
21
+ * emitter,
22
+ * userProvider
23
+ * )
24
+ *
25
+ * const user = await guard.authenticate()
26
+ * console.log('Authenticated user:', user.email)
27
+ */
8
28
  var SessionGuard = class {
29
+ /**
30
+ * A unique name for the guard.
31
+ */
9
32
  #name;
33
+ /**
34
+ * Reference to the current HTTP context
35
+ */
10
36
  #ctx;
37
+ /**
38
+ * Options accepted by the session guard
39
+ */
11
40
  #options;
41
+ /**
42
+ * Provider to lookup user details
43
+ */
12
44
  #userProvider;
45
+ /**
46
+ * Emitter to emit events
47
+ */
13
48
  #emitter;
49
+ /**
50
+ * Driver name of the guard
51
+ */
14
52
  driverName = "session";
53
+ /**
54
+ * Whether or not the authentication has been attempted
55
+ * during the current request.
56
+ */
15
57
  authenticationAttempted = false;
58
+ /**
59
+ * A boolean to know if a remember me token was used in attempt
60
+ * to login a user.
61
+ */
16
62
  attemptedViaRemember = false;
63
+ /**
64
+ * A boolean to know if the current request has
65
+ * been authenticated
66
+ */
17
67
  isAuthenticated = false;
68
+ /**
69
+ * A boolean to know if the current request is authenticated
70
+ * using the "rememember_me" token.
71
+ */
18
72
  viaRemember = false;
73
+ /**
74
+ * Find if the user has been logged out during
75
+ * the current request
76
+ */
19
77
  isLoggedOut = false;
78
+ /**
79
+ * Reference to an instance of the authenticated user.
80
+ * The value only exists after calling one of the
81
+ * following methods.
82
+ *
83
+ * - authenticate
84
+ * - check
85
+ *
86
+ * You can use the "getUserOrFail" method to throw an exception if
87
+ * the request is not authenticated.
88
+ */
20
89
  user;
90
+ /**
91
+ * The key used to store the logged-in user id inside
92
+ * session
93
+ *
94
+ * @example
95
+ * console.log('Session key:', guard.sessionKeyName) // 'auth_web'
96
+ */
21
97
  get sessionKeyName() {
22
98
  return `auth_${this.#name}`;
23
99
  }
100
+ /**
101
+ * The key used to store the remember me token cookie
102
+ *
103
+ * @example
104
+ * console.log('Remember me key:', guard.rememberMeKeyName) // 'remember_web'
105
+ */
24
106
  get rememberMeKeyName() {
25
107
  return `remember_${this.#name}`;
26
108
  }
109
+ /**
110
+ * Creates a new SessionGuard instance
111
+ *
112
+ * @param name - Unique name for the guard instance
113
+ * @param ctx - HTTP context for the current request
114
+ * @param options - Configuration options for the session guard
115
+ * @param emitter - Event emitter for guard events
116
+ * @param userProvider - User provider for authentication
117
+ *
118
+ * @example
119
+ * const guard = new SessionGuard(
120
+ * 'web',
121
+ * ctx,
122
+ * { useRememberMeTokens: true, rememberMeTokensAge: '30d' },
123
+ * emitter,
124
+ * userProvider
125
+ * )
126
+ */
27
127
  constructor(name, ctx, options, emitter, userProvider) {
28
128
  this.#name = name;
29
129
  this.#ctx = ctx;
@@ -34,10 +134,19 @@ var SessionGuard = class {
34
134
  this.#emitter = emitter;
35
135
  this.#userProvider = userProvider;
36
136
  }
137
+ /**
138
+ * Returns the session instance for the given request,
139
+ * ensuring the property exists
140
+ */
37
141
  #getSession() {
38
142
  if (!("session" in this.#ctx)) throw new RuntimeException("Cannot authenticate user. Install and configure \"@adonisjs/session\" package");
39
143
  return this.#ctx.session;
40
144
  }
145
+ /**
146
+ * Emits authentication failure, updates the local state,
147
+ * and returns an exception to end the authentication
148
+ * cycle.
149
+ */
41
150
  #authenticationFailed(sessionId) {
42
151
  this.isAuthenticated = false;
43
152
  this.viaRemember = false;
@@ -52,6 +161,10 @@ var SessionGuard = class {
52
161
  });
53
162
  return error;
54
163
  }
164
+ /**
165
+ * Emits the authentication succeeded event and updates
166
+ * the local state to reflect successful authentication
167
+ */
55
168
  #authenticationSucceeded(sessionId, user, rememberMeToken) {
56
169
  this.isAuthenticated = true;
57
170
  this.viaRemember = !!rememberMeToken;
@@ -65,6 +178,10 @@ var SessionGuard = class {
65
178
  rememberMeToken
66
179
  });
67
180
  }
181
+ /**
182
+ * Emits the login succeeded event and updates the login
183
+ * state
184
+ */
68
185
  #loginSucceeded(sessionId, user, rememberMeToken) {
69
186
  this.user = user;
70
187
  this.isLoggedOut = false;
@@ -76,39 +193,102 @@ var SessionGuard = class {
76
193
  rememberMeToken
77
194
  });
78
195
  }
196
+ /**
197
+ * Creates session for a given user by their user id.
198
+ */
79
199
  #createSessionForUser(userId) {
80
200
  const session = this.#getSession();
81
201
  session.put(this.sessionKeyName, userId);
82
202
  session.regenerate();
83
203
  }
204
+ /**
205
+ * Creates the remember me cookie
206
+ */
84
207
  #createRememberMeCookie(value) {
85
208
  this.#ctx.response.encryptedCookie(this.rememberMeKeyName, value.release(), {
86
209
  maxAge: this.#options.rememberMeTokensAge,
87
210
  httpOnly: true
88
211
  });
89
212
  }
213
+ /**
214
+ * Authenticates the user using its id read from the session
215
+ * store.
216
+ *
217
+ * - We check the user exists in the db
218
+ * - If not, throw exception.
219
+ * - Otherwise, update local state to mark the user as logged-in
220
+ */
90
221
  async #authenticateViaId(userId, sessionId) {
91
222
  const providerUser = await this.#userProvider.findById(userId);
92
223
  if (!providerUser) throw this.#authenticationFailed(sessionId);
93
224
  this.#authenticationSucceeded(sessionId, providerUser.getOriginal());
94
225
  return this.user;
95
226
  }
227
+ /**
228
+ * Authenticates user from the remember me cookie. Creates a fresh
229
+ * session for them and recycles the remember me token as well.
230
+ */
96
231
  async #authenticateViaRememberCookie(rememberMeCookie, sessionId) {
232
+ /**
233
+ * This method is only invoked when "options.useRememberTokens" is set to
234
+ * true and hence the user provider will have methods to manage tokens
235
+ */
97
236
  const userProvider = this.#userProvider;
237
+ /**
238
+ * Verify the token using the user provider.
239
+ */
98
240
  const token = await userProvider.verifyRememberToken(new Secret(rememberMeCookie));
99
241
  if (!token) throw this.#authenticationFailed(sessionId);
242
+ /**
243
+ * Check if a user for the token exists. Otherwise abort
244
+ * authentication
245
+ */
100
246
  const providerUser = await userProvider.findById(token.tokenableId);
101
247
  if (!providerUser) throw this.#authenticationFailed(sessionId);
248
+ /**
249
+ * Recycle remember token and the remember me cookie
250
+ */
102
251
  const recycledToken = await userProvider.recycleRememberToken(providerUser.getOriginal(), token.identifier, this.#options.rememberMeTokensAge);
252
+ /**
253
+ * Persist remember token inside the cookie
254
+ */
103
255
  this.#createRememberMeCookie(recycledToken.value);
256
+ /**
257
+ * Create session
258
+ */
104
259
  this.#createSessionForUser(providerUser.getId());
260
+ /**
261
+ * Emit event and update local state
262
+ */
105
263
  this.#authenticationSucceeded(sessionId, providerUser.getOriginal(), token);
106
264
  return this.user;
107
265
  }
266
+ /**
267
+ * Returns an instance of the authenticated user. Or throws
268
+ * an exception if the request is not authenticated.
269
+ *
270
+ * @throws {E_UNAUTHORIZED_ACCESS} When user is not authenticated
271
+ *
272
+ * @example
273
+ * const user = guard.getUserOrFail()
274
+ * console.log('User:', user.email)
275
+ */
108
276
  getUserOrFail() {
109
277
  if (!this.user) throw new E_UNAUTHORIZED_ACCESS("Invalid or expired user session", { guardDriverName: this.driverName });
110
278
  return this.user;
111
279
  }
280
+ /**
281
+ * Login user using sessions. Optionally, you can also create
282
+ * a remember me token to automatically login user when their
283
+ * session expires.
284
+ *
285
+ * @param user - The user to login
286
+ * @param remember - Whether to create a remember me token
287
+ *
288
+ * @example
289
+ * await guard.login(user, true)
290
+ * console.log('User logged in with remember me token')
291
+ */
112
292
  async login(user, remember = false) {
113
293
  const session = this.#getSession();
114
294
  const providerUser = await this.#userProvider.createUserForGuard(user);
@@ -117,39 +297,102 @@ var SessionGuard = class {
117
297
  user,
118
298
  guardName: this.#name
119
299
  });
300
+ /**
301
+ * Create remember me token and persist it with the provider
302
+ * when remember me token is true.
303
+ */
120
304
  let token;
121
305
  if (remember) {
122
306
  if (!this.#options.useRememberMeTokens) throw new RuntimeException("Cannot use \"rememberMe\" feature. It has been disabled");
123
307
  token = await this.#userProvider.createRememberToken(providerUser.getOriginal(), this.#options.rememberMeTokensAge);
124
308
  }
309
+ /**
310
+ * Persist remember token inside the cookie (if exists)
311
+ * Otherwise remove the cookie
312
+ */
125
313
  if (token) this.#createRememberMeCookie(token.value);
126
314
  else this.#ctx.response.clearCookie(this.rememberMeKeyName);
315
+ /**
316
+ * Create session
317
+ */
127
318
  this.#createSessionForUser(providerUser.getId());
319
+ /**
320
+ * Mark user as logged-in
321
+ */
128
322
  this.#loginSucceeded(session.sessionId, providerUser.getOriginal(), token);
129
323
  }
324
+ /**
325
+ * Logout a user by removing its state from the session
326
+ * store and delete the remember me cookie (if any).
327
+ *
328
+ * @example
329
+ * await guard.logout()
330
+ * console.log('User logged out successfully')
331
+ */
130
332
  async logout() {
131
333
  const session = this.#getSession();
132
334
  const rememberMeCookie = this.#ctx.request.encryptedCookie(this.rememberMeKeyName);
335
+ /**
336
+ * Clear client side state
337
+ */
133
338
  session.forget(this.sessionKeyName);
134
339
  this.#ctx.response.clearCookie(this.rememberMeKeyName);
340
+ /**
341
+ * Delete remember me token when
342
+ *
343
+ * - Tokens are enabled
344
+ * - A cookie exists
345
+ * - And we know about the user already
346
+ */
135
347
  if (this.user && rememberMeCookie && this.#options.useRememberMeTokens) {
348
+ /**
349
+ * Here we assume the userProvider has implemented APIs to manage remember
350
+ * me tokens, since the "useRememberMeTokens" flag is enabled.
351
+ */
136
352
  const userProvider = this.#userProvider;
137
353
  const token = await userProvider.verifyRememberToken(new Secret(rememberMeCookie));
138
354
  if (token) await userProvider.deleteRemeberToken(this.user, token.identifier);
139
355
  }
356
+ /**
357
+ * Notify the user has been logged out
358
+ */
140
359
  this.#emitter.emit("session_auth:logged_out", {
141
360
  ctx: this.#ctx,
142
361
  guardName: this.#name,
143
362
  user: this.user || null,
144
363
  sessionId: session.sessionId
145
364
  });
365
+ /**
366
+ * Update local state
367
+ */
146
368
  this.user = void 0;
147
369
  this.viaRemember = false;
148
370
  this.isAuthenticated = false;
149
371
  this.isLoggedOut = true;
150
372
  }
373
+ /**
374
+ * Authenticate the current HTTP request by verifying the session
375
+ * or remember me token and fails with an exception if authentication fails
376
+ *
377
+ * @throws {E_UNAUTHORIZED_ACCESS} When authentication fails
378
+ *
379
+ * @example
380
+ * try {
381
+ * const user = await guard.authenticate()
382
+ * console.log('Authenticated as:', user.email)
383
+ * } catch (error) {
384
+ * console.log('Authentication failed')
385
+ * }
386
+ */
151
387
  async authenticate() {
388
+ /**
389
+ * Return early when authentication has already been
390
+ * attempted
391
+ */
152
392
  if (this.authenticationAttempted) return this.getUserOrFail();
393
+ /**
394
+ * Notify we begin to attempt the authentication
395
+ */
153
396
  this.authenticationAttempted = true;
154
397
  const session = this.#getSession();
155
398
  this.#emitter.emit("session_auth:authentication_attempted", {
@@ -157,15 +400,38 @@ var SessionGuard = class {
157
400
  sessionId: session.sessionId,
158
401
  guardName: this.#name
159
402
  });
403
+ /**
404
+ * Check if there is a user id inside the session store.
405
+ * If yes, fetch the user from the persistent storage
406
+ * and mark them as logged-in
407
+ */
160
408
  const authUserId = session.get(this.sessionKeyName);
161
409
  if (authUserId) return this.#authenticateViaId(authUserId, session.sessionId);
410
+ /**
411
+ * If user provider supports remember me tokens and the remember me
412
+ * cookie exists, then attempt to login + authenticate via
413
+ * the remember me token.
414
+ */
162
415
  const rememberMeCookie = this.#ctx.request.encryptedCookie(this.rememberMeKeyName);
163
416
  if (rememberMeCookie && this.#options.useRememberMeTokens) {
164
417
  this.attemptedViaRemember = true;
165
418
  return this.#authenticateViaRememberCookie(rememberMeCookie, session.sessionId);
166
419
  }
420
+ /**
421
+ * Otherwise throw an exception
422
+ */
167
423
  throw this.#authenticationFailed(session.sessionId);
168
424
  }
425
+ /**
426
+ * Silently check if the user is authenticated or not, without
427
+ * throwing any exceptions
428
+ *
429
+ * @example
430
+ * const isAuthenticated = await guard.check()
431
+ * if (isAuthenticated) {
432
+ * console.log('User is authenticated:', guard.user.email)
433
+ * }
434
+ */
169
435
  async check() {
170
436
  try {
171
437
  await this.authenticate();
@@ -175,14 +441,63 @@ var SessionGuard = class {
175
441
  throw error;
176
442
  }
177
443
  }
444
+ /**
445
+ * Returns the session info for the clients to send during
446
+ * an HTTP request to mark the user as logged-in.
447
+ *
448
+ * @param user - The user to authenticate as
449
+ *
450
+ * @example
451
+ * const clientAuth = await guard.authenticateAsClient(user)
452
+ * // Use clientAuth.session in API tests
453
+ */
178
454
  async authenticateAsClient(user) {
179
455
  const userId = (await this.#userProvider.createUserForGuard(user)).getId();
180
456
  return { session: { [this.sessionKeyName]: userId } };
181
457
  }
182
458
  };
459
+ //#endregion
460
+ //#region modules/session_guard/remember_me_token.ts
461
+ /**
462
+ * Remember me token represents an opaque token that can be
463
+ * used to automatically login a user without asking them
464
+ * to re-login
465
+ *
466
+ * @example
467
+ * const token = new RememberMeToken({
468
+ * identifier: 1,
469
+ * tokenableId: 123,
470
+ * hash: 'sha256hash',
471
+ * createdAt: new Date(),
472
+ * updatedAt: new Date(),
473
+ * expiresAt: new Date(Date.now() + 86400000)
474
+ * })
475
+ */
183
476
  var RememberMeToken = class {
477
+ /**
478
+ * Decodes a publicly shared token and return the series
479
+ * and the token value from it.
480
+ *
481
+ * Returns null when unable to decode the token because of
482
+ * invalid format or encoding.
483
+ *
484
+ * @param value - The token value to decode
485
+ *
486
+ * @example
487
+ * const decoded = RememberMeToken.decode('abc123.def456')
488
+ * if (decoded) {
489
+ * console.log('Token ID:', decoded.identifier)
490
+ * console.log('Secret:', decoded.secret.release())
491
+ * }
492
+ */
184
493
  static decode(value) {
494
+ /**
495
+ * Ensure value is a string and starts with the prefix.
496
+ */
185
497
  if (typeof value !== "string") return null;
498
+ /**
499
+ * Remove prefix from the rest of the token.
500
+ */
186
501
  if (!value) return null;
187
502
  const [identifier, ...tokenValue] = value.split(".");
188
503
  if (!identifier || tokenValue.length === 0) return null;
@@ -194,6 +509,18 @@ var RememberMeToken = class {
194
509
  secret: new Secret(decodedSecret)
195
510
  };
196
511
  }
512
+ /**
513
+ * Creates a transient token that can be shared with the persistence
514
+ * layer.
515
+ *
516
+ * @param userId - The ID of the user for whom the token is created
517
+ * @param size - The size of the random secret to generate
518
+ * @param expiresIn - Expiration time (seconds or duration string)
519
+ *
520
+ * @example
521
+ * const transientToken = RememberMeToken.createTransientToken(123, 32, '30d')
522
+ * // Store transientToken in database
523
+ */
197
524
  static createTransientToken(userId, size, expiresIn) {
198
525
  const expiresAt = /* @__PURE__ */ new Date();
199
526
  expiresAt.setSeconds(expiresAt.getSeconds() + string.seconds.parse(expiresIn));
@@ -203,6 +530,16 @@ var RememberMeToken = class {
203
530
  ...this.seed(size)
204
531
  };
205
532
  }
533
+ /**
534
+ * Creates a secret opaque token and its hash.
535
+ *
536
+ * @param size - The size of the random string to generate
537
+ *
538
+ * @example
539
+ * const { secret, hash } = RememberMeToken.seed(32)
540
+ * console.log('Secret:', secret.release())
541
+ * console.log('Hash:', hash)
542
+ */
206
543
  static seed(size) {
207
544
  const secret = new Secret(string.random(size));
208
545
  return {
@@ -210,13 +547,54 @@ var RememberMeToken = class {
210
547
  hash: createHash("sha256").update(secret.release()).digest("hex")
211
548
  };
212
549
  }
550
+ /**
551
+ * Identifer is a unique sequence to identify the
552
+ * token within database. It should be the
553
+ * primary/unique key
554
+ */
213
555
  identifier;
556
+ /**
557
+ * Reference to the user id for whom the token
558
+ * is generated.
559
+ */
214
560
  tokenableId;
561
+ /**
562
+ * The value is a public representation of a token. It is created
563
+ * by combining the "identifier"."secret"
564
+ */
215
565
  value;
566
+ /**
567
+ * Hash is computed from the seed to later verify the validity
568
+ * of seed
569
+ */
216
570
  hash;
571
+ /**
572
+ * Date/time when the token instance was created
573
+ */
217
574
  createdAt;
575
+ /**
576
+ * Date/time when the token was updated
577
+ */
218
578
  updatedAt;
579
+ /**
580
+ * Timestamp at which the token will expire
581
+ */
219
582
  expiresAt;
583
+ /**
584
+ * Creates a new RememberMeToken instance
585
+ *
586
+ * @param attributes - Token attributes including identifier, user ID, hash, etc.
587
+ *
588
+ * @example
589
+ * const token = new RememberMeToken({
590
+ * identifier: 1,
591
+ * tokenableId: 123,
592
+ * hash: 'sha256hash',
593
+ * createdAt: new Date(),
594
+ * updatedAt: new Date(),
595
+ * expiresAt: new Date(Date.now() + 86400000)
596
+ * })
597
+ */
220
598
  constructor(attributes) {
221
599
  this.identifier = attributes.identifier;
222
600
  this.tokenableId = attributes.tokenableId;
@@ -224,38 +602,132 @@ var RememberMeToken = class {
224
602
  this.createdAt = attributes.createdAt;
225
603
  this.updatedAt = attributes.updatedAt;
226
604
  this.expiresAt = attributes.expiresAt;
605
+ /**
606
+ * Compute value when secret is provided
607
+ */
227
608
  if (attributes.secret) this.value = new Secret(`${base64.urlEncode(String(this.identifier))}.${base64.urlEncode(attributes.secret.release())}`);
228
609
  }
610
+ /**
611
+ * Check if the token has been expired. Verifies
612
+ * the "expiresAt" timestamp with the current
613
+ * date.
614
+ *
615
+ * @example
616
+ * if (token.isExpired()) {
617
+ * console.log('Remember me token has expired')
618
+ * } else {
619
+ * console.log('Token is still valid')
620
+ * }
621
+ */
229
622
  isExpired() {
230
623
  return this.expiresAt < /* @__PURE__ */ new Date();
231
624
  }
625
+ /**
626
+ * Verifies the value of a token against the pre-defined hash
627
+ *
628
+ * @param secret - The secret to verify against the stored hash
629
+ *
630
+ * @example
631
+ * const isValid = token.verify(new Secret('user-provided-secret'))
632
+ * if (isValid) {
633
+ * console.log('Remember me token is valid')
634
+ * }
635
+ */
232
636
  verify(secret) {
233
637
  const newHash = createHash("sha256").update(secret.release()).digest("hex");
234
638
  return safeEqual(this.hash, newHash);
235
639
  }
236
640
  };
641
+ //#endregion
642
+ //#region modules/session_guard/token_providers/db.ts
643
+ /**
644
+ * DbRememberMeTokensProvider uses lucid database service to fetch and
645
+ * persist tokens for a given user.
646
+ *
647
+ * The user must be an instance of the associated user model.
648
+ *
649
+ * @template TokenableModel - The Lucid model that can have remember me tokens
650
+ *
651
+ * @example
652
+ * const provider = new DbRememberMeTokensProvider({
653
+ * tokenableModel: () => import('#models/user'),
654
+ * table: 'remember_me_tokens',
655
+ * tokenSecretLength: 32
656
+ * })
657
+ */
237
658
  var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
659
+ /**
660
+ * Create tokens provider instance for a given Lucid model
661
+ *
662
+ * @param model - The tokenable model factory function
663
+ * @param options - Optional configuration options
664
+ *
665
+ * @example
666
+ * const provider = DbRememberMeTokensProvider.forModel(
667
+ * () => import('#models/user'),
668
+ * { table: 'user_remember_tokens' }
669
+ * )
670
+ */
238
671
  static forModel(model, options) {
239
672
  return new DbRememberMeTokensProvider({
240
673
  tokenableModel: model,
241
674
  ...options || {}
242
675
  });
243
676
  }
677
+ /**
678
+ * Database table to use for querying remember me tokens
679
+ */
244
680
  table;
681
+ /**
682
+ * The length for the token secret. A secret is a cryptographically
683
+ * secure random string.
684
+ */
245
685
  tokenSecretLength;
686
+ /**
687
+ * Creates a new DbRememberMeTokensProvider instance
688
+ *
689
+ * @param options - Configuration options for the provider
690
+ *
691
+ * @example
692
+ * const provider = new DbRememberMeTokensProvider({
693
+ * tokenableModel: () => import('#models/user'),
694
+ * table: 'remember_me_tokens',
695
+ * tokenSecretLength: 40
696
+ * })
697
+ */
246
698
  constructor(options) {
247
699
  this.options = options;
248
700
  this.table = options.table || "remember_me_tokens";
249
701
  this.tokenSecretLength = options.tokenSecretLength || 40;
250
702
  }
703
+ /**
704
+ * Check if value is an object
705
+ */
251
706
  #isObject(value) {
252
707
  return value !== null && typeof value === "object" && !Array.isArray(value);
253
708
  }
709
+ /**
710
+ * Ensure the provided user is an instance of the user model and
711
+ * has a primary key
712
+ */
254
713
  #ensureIsPersisted(user) {
255
714
  const model = this.options.tokenableModel;
256
715
  if (user instanceof model === false) throw new RuntimeException(`Invalid user object. It must be an instance of the "${model.name}" model`);
257
716
  if (!user.$primaryKeyValue) throw new RuntimeException(`Cannot use "${model.name}" model for managing remember me tokens. The value of column "${model.primaryKey}" is undefined or null`);
258
717
  }
718
+ /**
719
+ * Maps a database row to an instance token instance
720
+ *
721
+ * @param dbRow - The database row containing token data
722
+ *
723
+ * @example
724
+ * const token = provider.dbRowToRememberMeToken({
725
+ * id: 1,
726
+ * tokenable_id: 123,
727
+ * hash: 'sha256hash',
728
+ * // ... other columns
729
+ * })
730
+ */
259
731
  dbRowToRememberMeToken(dbRow) {
260
732
  return new RememberMeToken({
261
733
  identifier: dbRow.id,
@@ -266,14 +738,40 @@ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
266
738
  expiresAt: typeof dbRow.expires_at === "number" ? new Date(dbRow.expires_at) : dbRow.expires_at
267
739
  });
268
740
  }
741
+ /**
742
+ * Returns a query client instance from the parent model
743
+ *
744
+ * @example
745
+ * const db = await provider.getDb()
746
+ * const tokens = await db.from('remember_me_tokens').select('*')
747
+ */
269
748
  async getDb() {
270
749
  const model = this.options.tokenableModel;
271
750
  return model.$adapter.query(model).client;
272
751
  }
752
+ /**
753
+ * Create a token for a user
754
+ *
755
+ * @param user - The user instance to create a token for
756
+ * @param expiresIn - Token expiration time
757
+ *
758
+ * @example
759
+ * const token = await provider.create(user, '30d')
760
+ * console.log('Remember token:', token.value.release())
761
+ */
273
762
  async create(user, expiresIn) {
274
763
  this.#ensureIsPersisted(user);
275
764
  const queryClient = await this.getDb();
765
+ /**
766
+ * Creating a transient token. Transient token abstracts
767
+ * the logic of creating a random secure secret and its
768
+ * hash
769
+ */
276
770
  const transientToken = RememberMeToken.createTransientToken(user.$primaryKeyValue, this.tokenSecretLength, expiresIn);
771
+ /**
772
+ * Row to insert inside the database. We expect exactly these
773
+ * columns to exist.
774
+ */
277
775
  const dbRow = {
278
776
  tokenable_id: transientToken.userId,
279
777
  hash: transientToken.hash,
@@ -281,9 +779,19 @@ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
281
779
  updated_at: /* @__PURE__ */ new Date(),
282
780
  expires_at: transientToken.expiresAt
283
781
  };
782
+ /**
783
+ * Insert data to the database.
784
+ */
284
785
  const result = await queryClient.table(this.table).insert(dbRow).returning("id");
285
786
  const id = this.#isObject(result[0]) ? result[0].id : result[0];
787
+ /**
788
+ * Throw error when unable to find id in the return value of
789
+ * the insert query
790
+ */
286
791
  if (!id) throw new RuntimeException(`Cannot save access token. The result "${inspect(result)}" of insert query is unexpected`);
792
+ /**
793
+ * Convert db row to a remember token
794
+ */
287
795
  return new RememberMeToken({
288
796
  identifier: id,
289
797
  tokenableId: dbRow.tokenable_id,
@@ -294,6 +802,18 @@ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
294
802
  expiresAt: dbRow.expires_at
295
803
  });
296
804
  }
805
+ /**
806
+ * Find a token for a user by the token id
807
+ *
808
+ * @param user - The user instance that owns the token
809
+ * @param identifier - The token identifier to search for
810
+ *
811
+ * @example
812
+ * const token = await provider.find(user, 123)
813
+ * if (token) {
814
+ * console.log('Found token with id:', token.identifier)
815
+ * }
816
+ */
297
817
  async find(user, identifier) {
298
818
  this.#ensureIsPersisted(user);
299
819
  const dbRow = await (await this.getDb()).query().from(this.table).where({
@@ -303,6 +823,16 @@ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
303
823
  if (!dbRow) return null;
304
824
  return this.dbRowToRememberMeToken(dbRow);
305
825
  }
826
+ /**
827
+ * Delete a token by its id
828
+ *
829
+ * @param user - The user instance that owns the token
830
+ * @param identifier - The token identifier to delete
831
+ *
832
+ * @example
833
+ * const deletedCount = await provider.delete(user, 123)
834
+ * console.log('Deleted tokens:', deletedCount)
835
+ */
306
836
  async delete(user, identifier) {
307
837
  this.#ensureIsPersisted(user);
308
838
  return await (await this.getDb()).query().from(this.table).where({
@@ -310,46 +840,147 @@ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
310
840
  tokenable_id: user.$primaryKeyValue
311
841
  }).del().exec();
312
842
  }
843
+ /**
844
+ * Returns all the tokens a given user
845
+ *
846
+ * @param user - The user instance to get tokens for
847
+ *
848
+ * @example
849
+ * const tokens = await provider.all(user)
850
+ * console.log('User has', tokens.length, 'remember tokens')
851
+ * tokens.forEach(token => console.log(token.identifier))
852
+ */
313
853
  async all(user) {
314
854
  this.#ensureIsPersisted(user);
315
855
  return (await (await this.getDb()).query().from(this.table).where({ tokenable_id: user.$primaryKeyValue }).orderBy("id", "desc").exec()).map((dbRow) => {
316
856
  return this.dbRowToRememberMeToken(dbRow);
317
857
  });
318
858
  }
859
+ /**
860
+ * Verifies a publicly shared remember me token and returns an
861
+ * RememberMeToken for it.
862
+ *
863
+ * Returns null when unable to verify the token or find it
864
+ * inside the storage
865
+ *
866
+ * @param tokenValue - The token value to verify
867
+ *
868
+ * @example
869
+ * const token = await provider.verify(new Secret('rmt_abc123.def456'))
870
+ * if (token && !token.isExpired()) {
871
+ * console.log('Valid remember token for user:', token.tokenableId)
872
+ * }
873
+ */
319
874
  async verify(tokenValue) {
320
875
  const decodedToken = RememberMeToken.decode(tokenValue.release());
321
876
  if (!decodedToken) return null;
322
877
  const dbRow = await (await this.getDb()).query().from(this.table).where({ id: decodedToken.identifier }).limit(1).first();
323
878
  if (!dbRow) return null;
879
+ /**
880
+ * Convert to remember me token instance
881
+ */
324
882
  const rememberMeToken = this.dbRowToRememberMeToken(dbRow);
883
+ /**
884
+ * Ensure the token secret matches the token hash
885
+ */
325
886
  if (!rememberMeToken.verify(decodedToken.secret) || rememberMeToken.isExpired()) return null;
326
887
  return rememberMeToken;
327
888
  }
889
+ /**
890
+ * Recycles a remember me token by deleting the old one and
891
+ * creates a new one.
892
+ *
893
+ * Ideally, the recycle should update the existing token, but we
894
+ * skip that for now and come back to it later and handle race
895
+ * conditions as well.
896
+ *
897
+ * @param user - The user that owns the token
898
+ * @param identifier - The token identifier to recycle
899
+ * @param expiresIn - New expiration time
900
+ *
901
+ * @example
902
+ * const newToken = await provider.recycle(user, 123, '30d')
903
+ * console.log('Recycled token:', newToken.value.release())
904
+ */
328
905
  async recycle(user, identifier, expiresIn) {
329
906
  await this.delete(user, identifier);
330
907
  return this.create(user, expiresIn);
331
908
  }
332
909
  };
910
+ //#endregion
911
+ //#region modules/session_guard/user_providers/lucid.ts
912
+ /**
913
+ * Uses a lucid model to verify access tokens and find a user during
914
+ * authentication
915
+ *
916
+ * @template UserModel - The Lucid model representing the user
917
+ *
918
+ * @example
919
+ * const userProvider = new SessionLucidUserProvider({
920
+ * model: () => import('#models/user')
921
+ * })
922
+ */
333
923
  var SessionLucidUserProvider = class {
924
+ /**
925
+ * Reference to the lazily imported model
926
+ */
334
927
  model;
928
+ /**
929
+ * Creates a new SessionLucidUserProvider instance
930
+ *
931
+ * @param options - Configuration options for the user provider
932
+ *
933
+ * @example
934
+ * const provider = new SessionLucidUserProvider({
935
+ * model: () => import('#models/user')
936
+ * })
937
+ */
335
938
  constructor(options) {
336
939
  this.options = options;
337
940
  }
941
+ /**
942
+ * Imports the model from the provider, returns and caches it
943
+ * for further operations.
944
+ *
945
+ * @example
946
+ * const UserModel = await provider.getModel()
947
+ * const user = await UserModel.find(1)
948
+ */
338
949
  async getModel() {
339
950
  if (this.model && !("hot" in import.meta)) return this.model;
340
951
  this.model = (await this.options.model()).default;
341
952
  return this.model;
342
953
  }
954
+ /**
955
+ * Returns the tokens provider associated with the user model
956
+ *
957
+ * @example
958
+ * const tokensProvider = await provider.getTokensProvider()
959
+ * const token = await tokensProvider.create(user, '7d')
960
+ */
343
961
  async getTokensProvider() {
344
962
  const model = await this.getModel();
345
963
  if (!model.rememberMeTokens) throw new RuntimeException(`Cannot use "${model.name}" model for verifying remember me tokens. Make sure to assign a token provider to the model.`);
346
964
  return model.rememberMeTokens;
347
965
  }
966
+ /**
967
+ * Creates an adapter user for the guard
968
+ *
969
+ * @param user - The user model instance
970
+ *
971
+ * @example
972
+ * const guardUser = await provider.createUserForGuard(user)
973
+ * console.log('User ID:', guardUser.getId())
974
+ * console.log('Original user:', guardUser.getOriginal())
975
+ */
348
976
  async createUserForGuard(user) {
349
977
  const model = await this.getModel();
350
978
  if (user instanceof model === false) throw new RuntimeException(`Invalid user object. It must be an instance of the "${model.name}" model`);
351
979
  return {
352
980
  getId() {
981
+ /**
982
+ * Ensure user has a primary key
983
+ */
353
984
  if (!user.$primaryKeyValue) throw new RuntimeException(`Cannot use "${model.name}" model for authentication. The value of column "${model.primaryKey}" is undefined or null`);
354
985
  return user.$primaryKeyValue;
355
986
  },
@@ -358,24 +989,95 @@ var SessionLucidUserProvider = class {
358
989
  }
359
990
  };
360
991
  }
992
+ /**
993
+ * Finds a user by their primary key value
994
+ *
995
+ * @param identifier - The user identifier to search for
996
+ *
997
+ * @example
998
+ * const guardUser = await provider.findById(123)
999
+ * if (guardUser) {
1000
+ * const originalUser = guardUser.getOriginal()
1001
+ * console.log('Found user:', originalUser.email)
1002
+ * }
1003
+ */
361
1004
  async findById(identifier) {
362
1005
  const user = await (await this.getModel()).find(identifier);
363
1006
  if (!user) return null;
364
1007
  return this.createUserForGuard(user);
365
1008
  }
1009
+ /**
1010
+ * Creates a remember token for a given user
1011
+ *
1012
+ * @param user - The user to create a token for
1013
+ * @param expiresIn - Token expiration time
1014
+ *
1015
+ * @example
1016
+ * const token = await provider.createRememberToken(user, '30d')
1017
+ * console.log('Remember token:', token.value.release())
1018
+ */
366
1019
  async createRememberToken(user, expiresIn) {
367
1020
  return (await this.getTokensProvider()).create(user, expiresIn);
368
1021
  }
1022
+ /**
1023
+ * Verify a token by its publicly shared value
1024
+ *
1025
+ * @param tokenValue - The token value to verify
1026
+ *
1027
+ * @example
1028
+ * const token = await provider.verifyRememberToken(
1029
+ * new Secret('rmt_abc123.def456')
1030
+ * )
1031
+ * if (token && !token.isExpired()) {
1032
+ * console.log('Valid remember token for user:', token.tokenableId)
1033
+ * }
1034
+ */
369
1035
  async verifyRememberToken(tokenValue) {
370
1036
  return (await this.getTokensProvider()).verify(tokenValue);
371
1037
  }
1038
+ /**
1039
+ * Delete a token for a user by the token identifier
1040
+ *
1041
+ * @param user - The user that owns the token
1042
+ * @param identifier - The token identifier to delete
1043
+ *
1044
+ * @example
1045
+ * const deletedCount = await provider.deleteRemeberToken(user, 123)
1046
+ * console.log('Deleted tokens:', deletedCount)
1047
+ */
372
1048
  async deleteRemeberToken(user, identifier) {
373
1049
  return (await this.getTokensProvider()).delete(user, identifier);
374
1050
  }
1051
+ /**
1052
+ * Recycle a token for a user by the token identifier
1053
+ *
1054
+ * @param user - The user that owns the token
1055
+ * @param identifier - The token identifier to recycle
1056
+ * @param expiresIn - New expiration time
1057
+ *
1058
+ * @example
1059
+ * const newToken = await provider.recycleRememberToken(user, 123, '30d')
1060
+ * console.log('Recycled token:', newToken.value.release())
1061
+ */
375
1062
  async recycleRememberToken(user, identifier, expiresIn) {
376
1063
  return (await this.getTokensProvider()).recycle(user, identifier, expiresIn);
377
1064
  }
378
1065
  };
1066
+ //#endregion
1067
+ //#region modules/session_guard/define_config.ts
1068
+ /**
1069
+ * Configures session guard for authentication using HTTP sessions
1070
+ *
1071
+ * @param config - Configuration object containing the user provider and session options
1072
+ *
1073
+ * @example
1074
+ * const guard = sessionGuard({
1075
+ * useRememberMeTokens: false,
1076
+ * provider: sessionUserProvider({
1077
+ * model: () => import('#models/user')
1078
+ * })
1079
+ * })
1080
+ */
379
1081
  function sessionGuard(config) {
380
1082
  return { async resolver(name, app) {
381
1083
  const emitter = await app.container.make("emitter");
@@ -383,7 +1085,19 @@ function sessionGuard(config) {
383
1085
  return (ctx) => new SessionGuard(name, ctx, config, emitter, provider);
384
1086
  } };
385
1087
  }
1088
+ /**
1089
+ * Configures user provider that uses Lucid models to authenticate
1090
+ * users using sessions
1091
+ *
1092
+ * @param config - Configuration options for the Lucid user provider
1093
+ *
1094
+ * @example
1095
+ * const userProvider = sessionUserProvider({
1096
+ * model: () => import('#models/user')
1097
+ * })
1098
+ */
386
1099
  function sessionUserProvider(config) {
387
1100
  return new SessionLucidUserProvider(config);
388
1101
  }
1102
+ //#endregion
389
1103
  export { DbRememberMeTokensProvider, RememberMeToken, SessionGuard, SessionLucidUserProvider, sessionGuard, sessionUserProvider };