@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,11 +1,34 @@
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/access_tokens_guard/crc32.ts
9
+ /**
10
+ * We use CRC32 just to add a recognizable checksum to tokens. This helps
11
+ * secret scanning tools like https://docs.github.com/en/github/administering-a-repository/about-secret-scanning easily detect tokens generated by a given program.
12
+ *
13
+ * You can learn more about appending checksum to a hash here in this Github
14
+ * article. https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/
15
+ *
16
+ * Code taken from:
17
+ * https://github.com/tsxper/crc32/blob/main/src/CRC32.ts
18
+ */
19
+ /**
20
+ * CRC32 implementation for adding recognizable checksums to tokens.
21
+ * This helps secret scanning tools easily detect tokens generated by AdonisJS.
22
+ *
23
+ * @example
24
+ * const crc = new CRC32()
25
+ * const checksum = crc.calculate('my-secret-token')
26
+ * console.log('Checksum:', checksum)
27
+ */
8
28
  var CRC32 = class {
29
+ /**
30
+ * Lookup table calculated for 0xEDB88320 divisor
31
+ */
9
32
  #lookupTable = [
10
33
  0,
11
34
  1996959894,
@@ -284,21 +307,97 @@ var CRC32 = class {
284
307
  if (num >= 0) return num;
285
308
  return 4294967295 - num * -1 + 1;
286
309
  }
310
+ /**
311
+ * Calculate CRC32 checksum for a string input
312
+ *
313
+ * @param input - The string to calculate checksum for
314
+ *
315
+ * @example
316
+ * const crc = new CRC32()
317
+ * const checksum = crc.calculate('hello-world')
318
+ * console.log('CRC32:', checksum)
319
+ */
287
320
  calculate(input) {
288
321
  return this.forString(input);
289
322
  }
323
+ /**
324
+ * Calculate CRC32 checksum for a string
325
+ *
326
+ * @param input - The string to process
327
+ *
328
+ * @example
329
+ * const crc = new CRC32()
330
+ * const result = crc.forString('test-string')
331
+ */
290
332
  forString(input) {
291
333
  const bytes = this.#strToBytes(input);
292
334
  return this.forBytes(bytes);
293
335
  }
336
+ /**
337
+ * Calculate CRC32 checksum for byte array
338
+ *
339
+ * @param bytes - The byte array to process
340
+ * @param accumulator - Optional accumulator for chained calculations
341
+ *
342
+ * @example
343
+ * const crc = new CRC32()
344
+ * const bytes = new TextEncoder().encode('hello')
345
+ * const result = crc.forBytes(bytes)
346
+ */
294
347
  forBytes(bytes, accumulator) {
295
348
  const crc = this.#calculateBytes(bytes, accumulator);
296
349
  return this.#crcToUint(crc);
297
350
  }
298
351
  };
352
+ //#endregion
353
+ //#region modules/access_tokens_guard/access_token.ts
354
+ /**
355
+ * Access token represents a token created for a user to authenticate
356
+ * using the auth module.
357
+ *
358
+ * It encapsulates the logic of creating an opaque token, generating
359
+ * its hash and verifying its hash.
360
+ *
361
+ * @example
362
+ * const token = new AccessToken({
363
+ * identifier: 1,
364
+ * tokenableId: 123,
365
+ * type: 'api',
366
+ * hash: 'sha256hash',
367
+ * createdAt: new Date(),
368
+ * updatedAt: new Date(),
369
+ * lastUsedAt: null,
370
+ * expiresAt: null,
371
+ * name: 'API Token',
372
+ * abilities: ['read', 'write']
373
+ * })
374
+ */
299
375
  var AccessToken = class {
376
+ /**
377
+ * Decodes a publicly shared token and return the series
378
+ * and the token value from it.
379
+ *
380
+ * Returns null when unable to decode the token because of
381
+ * invalid format or encoding.
382
+ *
383
+ * @param prefix - The token prefix to validate against
384
+ * @param value - The token value to decode
385
+ *
386
+ * @example
387
+ * const decoded = AccessToken.decode('oat_', 'oat_abc123.def456')
388
+ * if (decoded) {
389
+ * console.log('Token ID:', decoded.identifier)
390
+ * console.log('Secret:', decoded.secret.release())
391
+ * }
392
+ */
300
393
  static decode(prefix, value) {
394
+ /**
395
+ * Ensure value is a string and starts with the prefix.
396
+ */
301
397
  if (typeof value !== "string" || !value.startsWith(`${prefix}`)) return null;
398
+ /**
399
+ * Remove prefix from the rest of the token.
400
+ */
302
401
  const token = value.replace(new RegExp(`^${prefix}`), "");
303
402
  if (!token) return null;
304
403
  const [identifier, ...tokenValue] = token.split(".");
@@ -311,6 +410,18 @@ var AccessToken = class {
311
410
  secret: new Secret(decodedSecret)
312
411
  };
313
412
  }
413
+ /**
414
+ * Creates a transient token that can be shared with the persistence
415
+ * layer.
416
+ *
417
+ * @param userId - The ID of the user for whom the token is created
418
+ * @param size - The size of the random secret to generate
419
+ * @param expiresIn - Optional expiration time (seconds or duration string)
420
+ *
421
+ * @example
422
+ * const transientToken = AccessToken.createTransientToken(123, 32, '7d')
423
+ * // Store transientToken in database
424
+ */
314
425
  static createTransientToken(userId, size, expiresIn) {
315
426
  let expiresAt;
316
427
  if (expiresIn) {
@@ -323,6 +434,18 @@ var AccessToken = class {
323
434
  ...this.seed(size)
324
435
  };
325
436
  }
437
+ /**
438
+ * Creates a secret opaque token and its hash. The secret is
439
+ * suffixed with a crc32 checksum for secret scanning tools
440
+ * to easily identify the token.
441
+ *
442
+ * @param size - The size of the random string to generate
443
+ *
444
+ * @example
445
+ * const { secret, hash } = AccessToken.seed(32)
446
+ * console.log('Secret:', secret.release())
447
+ * console.log('Hash:', hash)
448
+ */
326
449
  static seed(size) {
327
450
  const seed = string.random(size);
328
451
  const secret = new Secret(`${seed}${new CRC32().calculate(seed)}`);
@@ -331,17 +454,76 @@ var AccessToken = class {
331
454
  hash: createHash("sha256").update(secret.release()).digest("hex")
332
455
  };
333
456
  }
457
+ /**
458
+ * Identifer is a unique sequence to identify the
459
+ * token within database. It should be the
460
+ * primary/unique key
461
+ */
334
462
  identifier;
463
+ /**
464
+ * Reference to the user id for whom the token
465
+ * is generated.
466
+ */
335
467
  tokenableId;
468
+ /**
469
+ * The value is a public representation of a token. It is created
470
+ * by combining the "identifier"."secret"
471
+ */
336
472
  value;
473
+ /**
474
+ * Recognizable name for the token
475
+ */
337
476
  name;
477
+ /**
478
+ * A unique type to identify a bucket of tokens inside the
479
+ * storage layer.
480
+ */
338
481
  type;
482
+ /**
483
+ * Hash is computed from the seed to later verify the validity
484
+ * of seed
485
+ */
339
486
  hash;
487
+ /**
488
+ * Date/time when the token instance was created
489
+ */
340
490
  createdAt;
491
+ /**
492
+ * Date/time when the token was updated
493
+ */
341
494
  updatedAt;
495
+ /**
496
+ * Timestamp at which the token was used for authentication
497
+ */
342
498
  lastUsedAt;
499
+ /**
500
+ * Timestamp at which the token will expire
501
+ */
343
502
  expiresAt;
503
+ /**
504
+ * An array of abilities the token can perform. The abilities
505
+ * is an array of abritary string values
506
+ */
344
507
  abilities;
508
+ /**
509
+ * Creates a new AccessToken instance
510
+ *
511
+ * @param attributes - Token attributes including identifier, user ID, type, hash, etc.
512
+ *
513
+ * @example
514
+ * const token = new AccessToken({
515
+ * identifier: 1,
516
+ * tokenableId: 123,
517
+ * type: 'api',
518
+ * hash: 'sha256hash',
519
+ * createdAt: new Date(),
520
+ * updatedAt: new Date(),
521
+ * lastUsedAt: null,
522
+ * expiresAt: new Date(Date.now() + 86400000),
523
+ * name: 'Mobile App Token',
524
+ * abilities: ['read:posts', 'write:posts']
525
+ * })
526
+ */
345
527
  constructor(attributes) {
346
528
  this.identifier = attributes.identifier;
347
529
  this.tokenableId = attributes.tokenableId;
@@ -353,28 +535,95 @@ var AccessToken = class {
353
535
  this.expiresAt = attributes.expiresAt;
354
536
  this.lastUsedAt = attributes.lastUsedAt;
355
537
  this.abilities = attributes.abilities || ["*"];
538
+ /**
539
+ * Compute value when secret is provided
540
+ */
356
541
  if (attributes.secret) {
357
542
  if (!attributes.prefix) throw new RuntimeException("Cannot compute token value without the prefix");
358
543
  this.value = new Secret(`${attributes.prefix}${base64.urlEncode(String(this.identifier))}.${base64.urlEncode(attributes.secret.release())}`);
359
544
  }
360
545
  }
546
+ /**
547
+ * Check if the token allows the given ability.
548
+ *
549
+ * @param ability - The ability to check
550
+ *
551
+ * @example
552
+ * if (token.allows('read:posts')) {
553
+ * console.log('User can read posts')
554
+ * }
555
+ */
361
556
  allows(ability) {
362
557
  return this.abilities.includes(ability) || this.abilities.includes("*");
363
558
  }
559
+ /**
560
+ * Check if the token denies the ability.
561
+ *
562
+ * @param ability - The ability to check
563
+ *
564
+ * @example
565
+ * if (token.denies('delete:posts')) {
566
+ * console.log('User cannot delete posts')
567
+ * }
568
+ */
364
569
  denies(ability) {
365
570
  return !this.abilities.includes(ability) && !this.abilities.includes("*");
366
571
  }
572
+ /**
573
+ * Authorize ability access using the current access token
574
+ *
575
+ * @param ability - The ability to authorize
576
+ *
577
+ * @throws {E_UNAUTHORIZED_ACCESS} When the token denies the ability
578
+ *
579
+ * @example
580
+ * token.authorize('write:posts') // Throws if not allowed
581
+ * console.log('Authorization successful')
582
+ */
367
583
  authorize(ability) {
368
584
  if (this.denies(ability)) throw new E_UNAUTHORIZED_ACCESS("Unauthorized access", { guardDriverName: "access_tokens" });
369
585
  }
586
+ /**
587
+ * Check if the token has been expired. Verifies
588
+ * the "expiresAt" timestamp with the current
589
+ * date.
590
+ *
591
+ * Tokens with no expiry never expire
592
+ *
593
+ * @example
594
+ * if (token.isExpired()) {
595
+ * console.log('Token has expired')
596
+ * } else {
597
+ * console.log('Token is still valid')
598
+ * }
599
+ */
370
600
  isExpired() {
371
601
  if (!this.expiresAt) return false;
372
602
  return this.expiresAt < /* @__PURE__ */ new Date();
373
603
  }
604
+ /**
605
+ * Verifies the value of a token against the pre-defined hash
606
+ *
607
+ * @param secret - The secret to verify against the stored hash
608
+ *
609
+ * @example
610
+ * const isValid = token.verify(new Secret('user-provided-secret'))
611
+ * if (isValid) {
612
+ * console.log('Token is valid')
613
+ * }
614
+ */
374
615
  verify(secret) {
375
616
  const newHash = createHash("sha256").update(secret.release()).digest("hex");
376
617
  return safeEqual(this.hash, newHash);
377
618
  }
619
+ /**
620
+ * Converts the token to a JSON representation suitable for API responses
621
+ *
622
+ * @example
623
+ * const tokenData = token.toJSON()
624
+ * console.log(tokenData.type) // 'bearer'
625
+ * console.log(tokenData.token) // 'oat_abc123.def456'
626
+ */
378
627
  toJSON() {
379
628
  return {
380
629
  type: "bearer",
@@ -386,21 +635,95 @@ var AccessToken = class {
386
635
  };
387
636
  }
388
637
  };
638
+ //#endregion
639
+ //#region modules/access_tokens_guard/guard.ts
640
+ /**
641
+ * Implementation of access tokens guard for the Auth layer. The heavy lifting
642
+ * of verifying tokens is done by the user provider. However, the guard is
643
+ * used to seamlessly integrate with the auth layer of the package.
644
+ *
645
+ * @template UserProvider - The user provider contract
646
+ *
647
+ * @example
648
+ * const guard = new AccessTokensGuard(
649
+ * 'api',
650
+ * ctx,
651
+ * emitter,
652
+ * userProvider
653
+ * )
654
+ *
655
+ * const user = await guard.authenticate()
656
+ * console.log('Authenticated user:', user.email)
657
+ */
389
658
  var AccessTokensGuard = class {
659
+ /**
660
+ * A unique name for the guard.
661
+ */
390
662
  #name;
663
+ /**
664
+ * Reference to the current HTTP context
665
+ */
391
666
  #ctx;
667
+ /**
668
+ * Provider to lookup user details
669
+ */
392
670
  #userProvider;
671
+ /**
672
+ * Emitter to emit events
673
+ */
393
674
  #emitter;
675
+ /**
676
+ * Driver name of the guard
677
+ */
394
678
  driverName = "access_tokens";
679
+ /**
680
+ * Whether or not the authentication has been attempted
681
+ * during the current request.
682
+ */
395
683
  authenticationAttempted = false;
684
+ /**
685
+ * A boolean to know if the current request has
686
+ * been authenticated
687
+ */
396
688
  isAuthenticated = false;
689
+ /**
690
+ * Reference to an instance of the authenticated user.
691
+ * The value only exists after calling one of the
692
+ * following methods.
693
+ *
694
+ * - authenticate
695
+ * - check
696
+ *
697
+ * You can use the "getUserOrFail" method to throw an exception if
698
+ * the request is not authenticated.
699
+ */
397
700
  user;
701
+ /**
702
+ * Creates a new AccessTokensGuard instance
703
+ *
704
+ * @param name - Unique name for the guard instance
705
+ * @param ctx - HTTP context for the current request
706
+ * @param emitter - Event emitter for guard events
707
+ * @param userProvider - User provider for token verification
708
+ *
709
+ * @example
710
+ * const guard = new AccessTokensGuard(
711
+ * 'api',
712
+ * ctx,
713
+ * emitter,
714
+ * new TokenUserProvider()
715
+ * )
716
+ */
398
717
  constructor(name, ctx, emitter, userProvider) {
399
718
  this.#name = name;
400
719
  this.#ctx = ctx;
401
720
  this.#emitter = emitter;
402
721
  this.#userProvider = userProvider;
403
722
  }
723
+ /**
724
+ * Emits authentication failure and returns an exception
725
+ * to end the authentication cycle.
726
+ */
404
727
  #authenticationFailed() {
405
728
  const error = new E_UNAUTHORIZED_ACCESS("Unauthorized access", { guardDriverName: this.driverName });
406
729
  this.#emitter.emit("access_tokens_auth:authentication_failed", {
@@ -410,30 +733,82 @@ var AccessTokensGuard = class {
410
733
  });
411
734
  return error;
412
735
  }
736
+ /**
737
+ * Returns the bearer token from the request headers or fails
738
+ */
413
739
  #getBearerToken() {
414
740
  const [type, token] = this.#ctx.request.header("authorization", "").split(" ");
415
741
  if (!type || type.toLowerCase() !== "bearer" || !token) throw this.#authenticationFailed();
416
742
  return token;
417
743
  }
744
+ /**
745
+ * Returns an instance of the authenticated user. Or throws
746
+ * an exception if the request is not authenticated.
747
+ *
748
+ * @throws {E_UNAUTHORIZED_ACCESS} When user is not authenticated
749
+ *
750
+ * @example
751
+ * const user = guard.getUserOrFail()
752
+ * console.log('User ID:', user.id)
753
+ * console.log('Current token:', user.currentAccessToken.name)
754
+ */
418
755
  getUserOrFail() {
419
756
  if (!this.user) throw new E_UNAUTHORIZED_ACCESS("Unauthorized access", { guardDriverName: this.driverName });
420
757
  return this.user;
421
758
  }
759
+ /**
760
+ * Authenticate the current HTTP request by verifying the bearer
761
+ * token or fails with an exception
762
+ *
763
+ * @throws {E_UNAUTHORIZED_ACCESS} When authentication fails
764
+ *
765
+ * @example
766
+ * try {
767
+ * const user = await guard.authenticate()
768
+ * console.log('Authenticated as:', user.email)
769
+ * console.log('Token abilities:', user.currentAccessToken.abilities)
770
+ * } catch (error) {
771
+ * console.log('Authentication failed')
772
+ * }
773
+ */
422
774
  async authenticate() {
775
+ /**
776
+ * Return early when authentication has already
777
+ * been attempted
778
+ */
423
779
  if (this.authenticationAttempted) return this.getUserOrFail();
780
+ /**
781
+ * Notify we begin to attempt the authentication
782
+ */
424
783
  this.authenticationAttempted = true;
425
784
  this.#emitter.emit("access_tokens_auth:authentication_attempted", {
426
785
  ctx: this.#ctx,
427
786
  guardName: this.#name
428
787
  });
788
+ /**
789
+ * Decode token or fail when unable to do so
790
+ */
429
791
  const bearerToken = new Secret(this.#getBearerToken());
792
+ /**
793
+ * Verify for token via the user provider
794
+ */
430
795
  const token = await this.#userProvider.verifyToken(bearerToken);
431
796
  if (!token) throw this.#authenticationFailed();
797
+ /**
798
+ * Check if a user for the token exists. Otherwise abort
799
+ * authentication
800
+ */
432
801
  const providerUser = await this.#userProvider.findById(token.tokenableId);
433
802
  if (!providerUser) throw this.#authenticationFailed();
803
+ /**
804
+ * Update local state
805
+ */
434
806
  this.isAuthenticated = true;
435
807
  this.user = providerUser.getOriginal();
436
808
  this.user.currentAccessToken = token;
809
+ /**
810
+ * Notify
811
+ */
437
812
  this.#emitter.emit("access_tokens_auth:authentication_succeeded", {
438
813
  ctx: this.#ctx,
439
814
  token,
@@ -442,16 +817,61 @@ var AccessTokensGuard = class {
442
817
  });
443
818
  return this.user;
444
819
  }
820
+ /**
821
+ * Create a token for a user (sign in)
822
+ *
823
+ * @param user - The user to create a token for
824
+ * @param abilities - Optional array of abilities the token should have
825
+ * @param options - Optional token configuration
826
+ *
827
+ * @example
828
+ * const token = await guard.createToken(user, ['read', 'write'], {
829
+ * name: 'Mobile App',
830
+ * expiresIn: '7d'
831
+ * })
832
+ * console.log('Token:', token.value.release())
833
+ */
445
834
  async createToken(user, abilities, options) {
446
835
  return await this.#userProvider.createToken(user, abilities, options);
447
836
  }
837
+ /**
838
+ * Invalidates the currently authenticated token (sign out)
839
+ *
840
+ * @example
841
+ * await guard.invalidateToken()
842
+ * console.log('Token invalidated successfully')
843
+ */
448
844
  async invalidateToken() {
449
845
  const bearerToken = new Secret(this.#getBearerToken());
450
846
  return await this.#userProvider.invalidateToken(bearerToken);
451
847
  }
848
+ /**
849
+ * Returns the Authorization header clients can use to authenticate
850
+ * the request.
851
+ *
852
+ * @param user - The user to authenticate as
853
+ * @param abilities - Optional array of abilities
854
+ * @param options - Optional token configuration
855
+ *
856
+ * @example
857
+ * const clientAuth = await guard.authenticateAsClient(user, ['read'])
858
+ * // Use clientAuth.headers.authorization in API tests
859
+ */
452
860
  async authenticateAsClient(user, abilities, options) {
453
861
  return { headers: { authorization: `Bearer ${(await this.#userProvider.createToken(user, abilities, options)).value.release()}` } };
454
862
  }
863
+ /**
864
+ * Silently check if the user is authenticated or not. The
865
+ * method is same as the "authenticate" method but does not
866
+ * throw any exceptions.
867
+ *
868
+ * @example
869
+ * const isAuthenticated = await guard.check()
870
+ * if (isAuthenticated) {
871
+ * const user = guard.user
872
+ * console.log('User is authenticated:', user.email)
873
+ * }
874
+ */
455
875
  async check() {
456
876
  try {
457
877
  await this.authenticate();
@@ -462,17 +882,79 @@ var AccessTokensGuard = class {
462
882
  }
463
883
  }
464
884
  };
885
+ //#endregion
886
+ //#region modules/access_tokens_guard/token_providers/db.ts
887
+ /**
888
+ * DbAccessTokensProvider uses lucid database service to fetch and
889
+ * persist tokens for a given user.
890
+ *
891
+ * The user must be an instance of the associated user model.
892
+ *
893
+ * @template TokenableModel - The Lucid model that can have tokens
894
+ *
895
+ * @example
896
+ * const provider = new DbAccessTokensProvider({
897
+ * tokenableModel: () => import('#models/user'),
898
+ * table: 'api_tokens',
899
+ * type: 'api_token',
900
+ * prefix: 'api_'
901
+ * })
902
+ */
465
903
  var DbAccessTokensProvider = class DbAccessTokensProvider {
904
+ /**
905
+ * Create tokens provider instance for a given Lucid model
906
+ *
907
+ * @param model - The tokenable model factory function
908
+ * @param options - Optional configuration options
909
+ *
910
+ * @example
911
+ * const provider = DbAccessTokensProvider.forModel(
912
+ * () => import('#models/user'),
913
+ * { prefix: 'api_', type: 'api_token' }
914
+ * )
915
+ */
466
916
  static forModel(model, options) {
467
917
  return new DbAccessTokensProvider({
468
918
  tokenableModel: model,
469
919
  ...options || {}
470
920
  });
471
921
  }
922
+ /**
923
+ * A unique type for the value. The type is used to identify a
924
+ * bucket of tokens within the storage layer.
925
+ *
926
+ * Defaults to auth_token
927
+ */
472
928
  type;
929
+ /**
930
+ * A unique prefix to append to the publicly shared token value.
931
+ *
932
+ * Defaults to oat
933
+ */
473
934
  prefix;
935
+ /**
936
+ * Database table to use for querying access tokens
937
+ */
474
938
  table;
939
+ /**
940
+ * The length for the token secret. A secret is a cryptographically
941
+ * secure random string.
942
+ */
475
943
  tokenSecretLength;
944
+ /**
945
+ * Creates a new DbAccessTokensProvider instance
946
+ *
947
+ * @param options - Configuration options for the provider
948
+ *
949
+ * @example
950
+ * const provider = new DbAccessTokensProvider({
951
+ * tokenableModel: () => import('#models/user'),
952
+ * table: 'auth_access_tokens',
953
+ * tokenSecretLength: 40,
954
+ * type: 'auth_token',
955
+ * prefix: 'oat_'
956
+ * })
957
+ */
476
958
  constructor(options) {
477
959
  this.options = options;
478
960
  this.table = options.table || "auth_access_tokens";
@@ -480,14 +962,35 @@ var DbAccessTokensProvider = class DbAccessTokensProvider {
480
962
  this.type = options.type || "auth_token";
481
963
  this.prefix = options.prefix || "oat_";
482
964
  }
965
+ /**
966
+ * Check if value is an object
967
+ */
483
968
  #isObject(value) {
484
969
  return value !== null && typeof value === "object" && !Array.isArray(value);
485
970
  }
971
+ /**
972
+ * Ensure the provided user is an instance of the user model and
973
+ * has a primary key
974
+ */
486
975
  #ensureIsPersisted(user) {
487
976
  const model = this.options.tokenableModel;
488
977
  if (user instanceof model === false) throw new RuntimeException(`Invalid user object. It must be an instance of the "${model.name}" model`);
489
978
  if (!user.$primaryKeyValue) throw new RuntimeException(`Cannot use "${model.name}" model for managing access tokens. The value of column "${model.primaryKey}" is undefined or null`);
490
979
  }
980
+ /**
981
+ * Maps a database row to an AccessToken instance
982
+ *
983
+ * @param dbRow - The database row containing token data
984
+ *
985
+ * @example
986
+ * const token = provider.dbRowToAccessToken({
987
+ * id: 1,
988
+ * tokenable_id: 123,
989
+ * type: 'auth_token',
990
+ * hash: 'sha256hash',
991
+ * // ... other columns
992
+ * })
993
+ */
491
994
  dbRowToAccessToken(dbRow) {
492
995
  return new AccessToken({
493
996
  identifier: dbRow.id,
@@ -502,14 +1005,44 @@ var DbAccessTokensProvider = class DbAccessTokensProvider {
502
1005
  expiresAt: typeof dbRow.expires_at === "number" ? new Date(dbRow.expires_at) : dbRow.expires_at
503
1006
  });
504
1007
  }
1008
+ /**
1009
+ * Returns a query client instance from the parent model
1010
+ *
1011
+ * @example
1012
+ * const db = await provider.getDb()
1013
+ * const tokens = await db.from('auth_access_tokens').select('*')
1014
+ */
505
1015
  async getDb() {
506
1016
  const model = this.options.tokenableModel;
507
1017
  return model.$adapter.query(model).client;
508
1018
  }
1019
+ /**
1020
+ * Create a token for a user
1021
+ *
1022
+ * @param user - The user instance to create a token for
1023
+ * @param abilities - Array of abilities the token should have
1024
+ * @param options - Optional token configuration
1025
+ *
1026
+ * @example
1027
+ * const token = await provider.create(user, ['read', 'write'], {
1028
+ * name: 'Mobile App Token',
1029
+ * expiresIn: '7d'
1030
+ * })
1031
+ * console.log('Token:', token.value.release())
1032
+ */
509
1033
  async create(user, abilities = ["*"], options) {
510
1034
  this.#ensureIsPersisted(user);
511
1035
  const queryClient = await this.getDb();
1036
+ /**
1037
+ * Creating a transient token. Transient token abstracts
1038
+ * the logic of creating a random secure secret and its
1039
+ * hash
1040
+ */
512
1041
  const transientToken = AccessToken.createTransientToken(user.$primaryKeyValue, this.tokenSecretLength, options?.expiresIn || this.options.expiresIn);
1042
+ /**
1043
+ * Row to insert inside the database. We expect exactly these
1044
+ * columns to exist.
1045
+ */
513
1046
  const dbRow = {
514
1047
  tokenable_id: transientToken.userId,
515
1048
  type: this.type,
@@ -521,9 +1054,19 @@ var DbAccessTokensProvider = class DbAccessTokensProvider {
521
1054
  last_used_at: null,
522
1055
  expires_at: transientToken.expiresAt || null
523
1056
  };
1057
+ /**
1058
+ * Insert data to the database.
1059
+ */
524
1060
  const result = await queryClient.table(this.table).insert(dbRow).returning("id");
525
1061
  const id = this.#isObject(result[0]) ? result[0].id : result[0];
1062
+ /**
1063
+ * Throw error when unable to find id in the return value of
1064
+ * the insert query
1065
+ */
526
1066
  if (!id) throw new RuntimeException(`Cannot save access token. The result "${inspect(result)}" of insert query is unexpected`);
1067
+ /**
1068
+ * Convert db row to an access token
1069
+ */
527
1070
  return new AccessToken({
528
1071
  identifier: id,
529
1072
  tokenableId: dbRow.tokenable_id,
@@ -539,6 +1082,18 @@ var DbAccessTokensProvider = class DbAccessTokensProvider {
539
1082
  expiresAt: dbRow.expires_at
540
1083
  });
541
1084
  }
1085
+ /**
1086
+ * Find a token for a user by the token id
1087
+ *
1088
+ * @param user - The user instance that owns the token
1089
+ * @param identifier - The token identifier to search for
1090
+ *
1091
+ * @example
1092
+ * const token = await provider.find(user, 123)
1093
+ * if (token) {
1094
+ * console.log('Found token:', token.name)
1095
+ * }
1096
+ */
542
1097
  async find(user, identifier) {
543
1098
  this.#ensureIsPersisted(user);
544
1099
  const dbRow = await (await this.getDb()).query().from(this.table).where({
@@ -549,6 +1104,16 @@ var DbAccessTokensProvider = class DbAccessTokensProvider {
549
1104
  if (!dbRow) return null;
550
1105
  return this.dbRowToAccessToken(dbRow);
551
1106
  }
1107
+ /**
1108
+ * Delete a token by its id
1109
+ *
1110
+ * @param user - The user instance that owns the token
1111
+ * @param identifier - The token identifier to delete
1112
+ *
1113
+ * @example
1114
+ * const deletedCount = await provider.delete(user, 123)
1115
+ * console.log('Deleted tokens:', deletedCount)
1116
+ */
552
1117
  async delete(user, identifier) {
553
1118
  this.#ensureIsPersisted(user);
554
1119
  return await (await this.getDb()).query().from(this.table).where({
@@ -557,6 +1122,15 @@ var DbAccessTokensProvider = class DbAccessTokensProvider {
557
1122
  type: this.type
558
1123
  }).del().exec();
559
1124
  }
1125
+ /**
1126
+ * Delete all tokens for a given user
1127
+ *
1128
+ * @param user - The user instance to delete tokens for
1129
+ *
1130
+ * @example
1131
+ * const deletedCount = await provider.deleteAll(user)
1132
+ * console.log('Deleted tokens:', deletedCount)
1133
+ */
560
1134
  async deleteAll(user) {
561
1135
  this.#ensureIsPersisted(user);
562
1136
  return await (await this.getDb()).query().from(this.table).where({
@@ -564,6 +1138,16 @@ var DbAccessTokensProvider = class DbAccessTokensProvider {
564
1138
  type: this.type
565
1139
  }).del().exec();
566
1140
  }
1141
+ /**
1142
+ * Returns all the tokens for a given user
1143
+ *
1144
+ * @param user - The user instance to get tokens for
1145
+ *
1146
+ * @example
1147
+ * const tokens = await provider.all(user)
1148
+ * console.log('User has', tokens.length, 'tokens')
1149
+ * tokens.forEach(token => console.log(token.name))
1150
+ */
567
1151
  async all(user) {
568
1152
  this.#ensureIsPersisted(user);
569
1153
  return (await (await this.getDb()).query().from(this.table).where({
@@ -585,6 +1169,21 @@ var DbAccessTokensProvider = class DbAccessTokensProvider {
585
1169
  return this.dbRowToAccessToken(dbRow);
586
1170
  });
587
1171
  }
1172
+ /**
1173
+ * Verifies a publicly shared access token and returns an
1174
+ * access token for it.
1175
+ *
1176
+ * Returns null when unable to verify the token or find it
1177
+ * inside the storage
1178
+ *
1179
+ * @param tokenValue - The token value to verify
1180
+ *
1181
+ * @example
1182
+ * const token = await provider.verify(new Secret('oat_abc123.def456'))
1183
+ * if (token && !token.isExpired()) {
1184
+ * console.log('Valid token for user:', token.tokenableId)
1185
+ * }
1186
+ */
588
1187
  async verify(tokenValue) {
589
1188
  const decodedToken = AccessToken.decode(this.prefix, tokenValue.release());
590
1189
  if (!decodedToken) return null;
@@ -594,15 +1193,35 @@ var DbAccessTokensProvider = class DbAccessTokensProvider {
594
1193
  type: this.type
595
1194
  }).limit(1).first();
596
1195
  if (!dbRow) return null;
1196
+ /**
1197
+ * Update last time the token is used
1198
+ */
597
1199
  dbRow.last_used_at = /* @__PURE__ */ new Date();
598
1200
  await db.from(this.table).where({
599
1201
  id: dbRow.id,
600
1202
  type: dbRow.type
601
1203
  }).update({ last_used_at: dbRow.last_used_at });
1204
+ /**
1205
+ * Convert to access token instance
1206
+ */
602
1207
  const accessToken = this.dbRowToAccessToken(dbRow);
1208
+ /**
1209
+ * Ensure the token secret matches the token hash
1210
+ */
603
1211
  if (!accessToken.verify(decodedToken.secret) || accessToken.isExpired()) return null;
604
1212
  return accessToken;
605
1213
  }
1214
+ /**
1215
+ * Invalidates a token identified by its publicly shared token
1216
+ *
1217
+ * @param tokenValue - The token value to invalidate
1218
+ *
1219
+ * @example
1220
+ * const wasInvalidated = await provider.invalidate(new Secret('oat_abc123.def456'))
1221
+ * if (wasInvalidated) {
1222
+ * console.log('Token successfully invalidated')
1223
+ * }
1224
+ */
606
1225
  async invalidate(tokenValue) {
607
1226
  const decodedToken = AccessToken.decode(this.prefix, tokenValue.release());
608
1227
  if (!decodedToken) return false;
@@ -613,26 +1232,83 @@ var DbAccessTokensProvider = class DbAccessTokensProvider {
613
1232
  return Boolean(deleteCount);
614
1233
  }
615
1234
  };
1235
+ //#endregion
1236
+ //#region modules/access_tokens_guard/user_providers/lucid.ts
1237
+ /**
1238
+ * Uses a lucid model to verify access tokens and find a user during
1239
+ * authentication
1240
+ *
1241
+ * @template TokenableProperty - The property name that holds the tokens provider
1242
+ * @template UserModel - The Lucid model representing the user
1243
+ *
1244
+ * @example
1245
+ * const userProvider = new AccessTokensLucidUserProvider({
1246
+ * model: () => import('#models/user'),
1247
+ * tokens: 'accessTokens'
1248
+ * })
1249
+ */
616
1250
  var AccessTokensLucidUserProvider = class {
1251
+ /**
1252
+ * Reference to the lazily imported model
1253
+ */
617
1254
  model;
1255
+ /**
1256
+ * Creates a new AccessTokensLucidUserProvider instance
1257
+ *
1258
+ * @param options - Configuration options for the user provider
1259
+ *
1260
+ * @example
1261
+ * const provider = new AccessTokensLucidUserProvider({
1262
+ * model: () => import('#models/user'),
1263
+ * tokens: 'accessTokens'
1264
+ * })
1265
+ */
618
1266
  constructor(options) {
619
1267
  this.options = options;
620
1268
  }
1269
+ /**
1270
+ * Imports the model from the provider, returns and caches it
1271
+ * for further operations.
1272
+ *
1273
+ * @example
1274
+ * const UserModel = await provider.getModel()
1275
+ * const user = await UserModel.find(1)
1276
+ */
621
1277
  async getModel() {
622
1278
  if (this.model && !("hot" in import.meta)) return this.model;
623
1279
  this.model = (await this.options.model()).default;
624
1280
  return this.model;
625
1281
  }
1282
+ /**
1283
+ * Returns the tokens provider associated with the user model
1284
+ *
1285
+ * @example
1286
+ * const tokensProvider = await provider.getTokensProvider()
1287
+ * const token = await tokensProvider.create(user, ['read'])
1288
+ */
626
1289
  async getTokensProvider() {
627
1290
  const model = await this.getModel();
628
1291
  if (!model[this.options.tokens]) throw new RuntimeException(`Cannot use "${model.name}" model for verifying access tokens. Make sure to assign a token provider to the model.`);
629
1292
  return model[this.options.tokens];
630
1293
  }
1294
+ /**
1295
+ * Creates an adapter user for the guard
1296
+ *
1297
+ * @param user - The user model instance
1298
+ *
1299
+ * @example
1300
+ * const guardUser = await provider.createUserForGuard(user)
1301
+ * console.log('User ID:', guardUser.getId())
1302
+ * console.log('Original user:', guardUser.getOriginal())
1303
+ */
631
1304
  async createUserForGuard(user) {
632
1305
  const model = await this.getModel();
633
1306
  if (user instanceof model === false) throw new RuntimeException(`Invalid user object. It must be an instance of the "${model.name}" model`);
634
1307
  return {
635
1308
  getId() {
1309
+ /**
1310
+ * Ensure user has a primary key
1311
+ */
636
1312
  if (!user.$primaryKeyValue) throw new RuntimeException(`Cannot use "${model.name}" model for authentication. The value of column "${model.primaryKey}" is undefined or null`);
637
1313
  return user.$primaryKeyValue;
638
1314
  },
@@ -641,21 +1317,87 @@ var AccessTokensLucidUserProvider = class {
641
1317
  }
642
1318
  };
643
1319
  }
1320
+ /**
1321
+ * Create a token for a given user
1322
+ *
1323
+ * @param user - The user to create a token for
1324
+ * @param abilities - Optional array of abilities the token should have
1325
+ * @param options - Optional token configuration
1326
+ *
1327
+ * @example
1328
+ * const token = await provider.createToken(user, ['read', 'write'], {
1329
+ * name: 'API Token',
1330
+ * expiresIn: '30d'
1331
+ * })
1332
+ * console.log('Created token:', token.value.release())
1333
+ */
644
1334
  async createToken(user, abilities, options) {
645
1335
  return (await this.getTokensProvider()).create(user, abilities, options);
646
1336
  }
1337
+ /**
1338
+ * Invalidates a token identified by its publicly shared token
1339
+ *
1340
+ * @param tokenValue - The token value to invalidate
1341
+ *
1342
+ * @example
1343
+ * const wasInvalidated = await provider.invalidateToken(
1344
+ * new Secret('oat_abc123.def456')
1345
+ * )
1346
+ * console.log('Token invalidated:', wasInvalidated)
1347
+ */
647
1348
  async invalidateToken(tokenValue) {
648
1349
  return (await this.getTokensProvider()).invalidate(tokenValue);
649
1350
  }
1351
+ /**
1352
+ * Finds a user by the user id
1353
+ *
1354
+ * @param identifier - The user identifier to search for
1355
+ *
1356
+ * @example
1357
+ * const guardUser = await provider.findById(123)
1358
+ * if (guardUser) {
1359
+ * const originalUser = guardUser.getOriginal()
1360
+ * console.log('Found user:', originalUser.email)
1361
+ * }
1362
+ */
650
1363
  async findById(identifier) {
651
1364
  const user = await (await this.getModel()).find(identifier);
652
1365
  if (!user) return null;
653
1366
  return this.createUserForGuard(user);
654
1367
  }
1368
+ /**
1369
+ * Verifies a publicly shared access token and returns an
1370
+ * access token for it.
1371
+ *
1372
+ * @param tokenValue - The token value to verify
1373
+ *
1374
+ * @example
1375
+ * const token = await provider.verifyToken(
1376
+ * new Secret('oat_abc123.def456')
1377
+ * )
1378
+ * if (token && !token.isExpired()) {
1379
+ * console.log('Valid token with abilities:', token.abilities)
1380
+ * }
1381
+ */
655
1382
  async verifyToken(tokenValue) {
656
1383
  return (await this.getTokensProvider()).verify(tokenValue);
657
1384
  }
658
1385
  };
1386
+ //#endregion
1387
+ //#region modules/access_tokens_guard/define_config.ts
1388
+ /**
1389
+ * Configures access tokens guard for authentication
1390
+ *
1391
+ * @param config - Configuration object containing the user provider
1392
+ *
1393
+ * @example
1394
+ * const guard = tokensGuard({
1395
+ * provider: tokensUserProvider({
1396
+ * model: () => import('#models/user'),
1397
+ * tokens: 'accessTokens'
1398
+ * })
1399
+ * })
1400
+ */
659
1401
  function tokensGuard(config) {
660
1402
  return { async resolver(name, app) {
661
1403
  const emitter = await app.container.make("emitter");
@@ -663,7 +1405,20 @@ function tokensGuard(config) {
663
1405
  return (ctx) => new AccessTokensGuard(name, ctx, emitter, provider);
664
1406
  } };
665
1407
  }
1408
+ /**
1409
+ * Configures user provider that uses Lucid models to verify
1410
+ * access tokens and find users during authentication.
1411
+ *
1412
+ * @param config - Configuration options for the Lucid user provider
1413
+ *
1414
+ * @example
1415
+ * const userProvider = tokensUserProvider({
1416
+ * model: () => import('#models/user'),
1417
+ * tokens: 'accessTokens'
1418
+ * })
1419
+ */
666
1420
  function tokensUserProvider(config) {
667
1421
  return new AccessTokensLucidUserProvider(config);
668
1422
  }
1423
+ //#endregion
669
1424
  export { AccessToken, AccessTokensGuard, AccessTokensLucidUserProvider, DbAccessTokensProvider, tokensGuard, tokensUserProvider };