@warlock.js/auth 5.11.0 → 5.13.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.
Files changed (185) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +88 -86
  3. package/esm/commands/auth-cleanup-command.mjs +1 -1
  4. package/esm/commands/auth-cleanup-command.mjs.map +1 -1
  5. package/esm/contracts/index.d.mts +4 -2
  6. package/esm/contracts/providers.d.mts +101 -0
  7. package/esm/contracts/providers.d.mts.map +1 -0
  8. package/esm/contracts/request-locals.d.mts +43 -0
  9. package/esm/contracts/request-locals.d.mts.map +1 -0
  10. package/esm/contracts/types.d.mts +159 -1
  11. package/esm/contracts/types.d.mts.map +1 -1
  12. package/esm/contracts/types.mjs.map +1 -1
  13. package/esm/errors/auth-provider-sdk-missing.error.d.mts +17 -0
  14. package/esm/errors/auth-provider-sdk-missing.error.d.mts.map +1 -0
  15. package/esm/errors/auth-provider-sdk-missing.error.mjs +22 -0
  16. package/esm/errors/auth-provider-sdk-missing.error.mjs.map +1 -0
  17. package/esm/errors/email-not-verified.error.d.mts +16 -0
  18. package/esm/errors/email-not-verified.error.d.mts.map +1 -0
  19. package/esm/errors/email-not-verified.error.mjs +20 -0
  20. package/esm/errors/email-not-verified.error.mjs.map +1 -0
  21. package/esm/errors/index.d.mts +7 -0
  22. package/esm/errors/index.mjs +9 -0
  23. package/esm/errors/invalid-one-time-token.error.d.mts +17 -0
  24. package/esm/errors/invalid-one-time-token.error.d.mts.map +1 -0
  25. package/esm/errors/invalid-one-time-token.error.mjs +21 -0
  26. package/esm/errors/invalid-one-time-token.error.mjs.map +1 -0
  27. package/esm/errors/invalid-passkey.error.d.mts +18 -0
  28. package/esm/errors/invalid-passkey.error.d.mts.map +1 -0
  29. package/esm/errors/invalid-passkey.error.mjs +23 -0
  30. package/esm/errors/invalid-passkey.error.mjs.map +1 -0
  31. package/esm/errors/invalid-provider-callback.error.d.mts +18 -0
  32. package/esm/errors/invalid-provider-callback.error.d.mts.map +1 -0
  33. package/esm/errors/invalid-provider-callback.error.mjs +23 -0
  34. package/esm/errors/invalid-provider-callback.error.mjs.map +1 -0
  35. package/esm/errors/notifications-unavailable.error.d.mts +15 -0
  36. package/esm/errors/notifications-unavailable.error.d.mts.map +1 -0
  37. package/esm/errors/notifications-unavailable.error.mjs +20 -0
  38. package/esm/errors/notifications-unavailable.error.mjs.map +1 -0
  39. package/esm/errors/provider-email-not-verified.error.d.mts +16 -0
  40. package/esm/errors/provider-email-not-verified.error.d.mts.map +1 -0
  41. package/esm/errors/provider-email-not-verified.error.mjs +20 -0
  42. package/esm/errors/provider-email-not-verified.error.mjs.map +1 -0
  43. package/esm/index.d.mts +31 -3
  44. package/esm/index.mjs +29 -1
  45. package/esm/middleware/auth.middleware.d.mts.map +1 -1
  46. package/esm/middleware/auth.middleware.mjs +13 -2
  47. package/esm/middleware/auth.middleware.mjs.map +1 -1
  48. package/esm/middleware/csrf-origin-check.d.mts +40 -0
  49. package/esm/middleware/csrf-origin-check.d.mts.map +1 -0
  50. package/esm/middleware/csrf-origin-check.mjs +105 -0
  51. package/esm/middleware/csrf-origin-check.mjs.map +1 -0
  52. package/esm/middleware/index.d.mts +5 -1
  53. package/esm/middleware/index.mjs +4 -0
  54. package/esm/middleware/one-time-token-throttle.middleware.d.mts +36 -0
  55. package/esm/middleware/one-time-token-throttle.middleware.d.mts.map +1 -0
  56. package/esm/middleware/one-time-token-throttle.middleware.mjs +53 -0
  57. package/esm/middleware/one-time-token-throttle.middleware.mjs.map +1 -0
  58. package/esm/middleware/otp-throttle.middleware.d.mts +30 -0
  59. package/esm/middleware/otp-throttle.middleware.d.mts.map +1 -0
  60. package/esm/middleware/otp-throttle.middleware.mjs +45 -0
  61. package/esm/middleware/otp-throttle.middleware.mjs.map +1 -0
  62. package/esm/middleware/require-verified-email.middleware.d.mts +18 -0
  63. package/esm/middleware/require-verified-email.middleware.d.mts.map +1 -0
  64. package/esm/middleware/require-verified-email.middleware.mjs +24 -0
  65. package/esm/middleware/require-verified-email.middleware.mjs.map +1 -0
  66. package/esm/models/access-token/access-token.model.d.mts +8 -0
  67. package/esm/models/access-token/access-token.model.d.mts.map +1 -1
  68. package/esm/models/access-token/access-token.model.mjs +3 -0
  69. package/esm/models/access-token/access-token.model.mjs.map +1 -1
  70. package/esm/models/index.d.mts +3 -1
  71. package/esm/models/index.d.mts.map +1 -1
  72. package/esm/models/index.mjs +16 -1
  73. package/esm/models/index.mjs.map +1 -1
  74. package/esm/models/one-time-token/index.d.mts +1 -0
  75. package/esm/models/one-time-token/index.mjs +3 -0
  76. package/esm/models/one-time-token/migration.mjs +31 -0
  77. package/esm/models/one-time-token/migration.mjs.map +1 -0
  78. package/esm/models/one-time-token/one-time-token.model.d.mts +173 -0
  79. package/esm/models/one-time-token/one-time-token.model.d.mts.map +1 -0
  80. package/esm/models/one-time-token/one-time-token.model.mjs +151 -0
  81. package/esm/models/one-time-token/one-time-token.model.mjs.map +1 -0
  82. package/esm/models/passkey-credential/index.d.mts +1 -0
  83. package/esm/models/passkey-credential/index.mjs +3 -0
  84. package/esm/models/passkey-credential/migration.mjs +25 -0
  85. package/esm/models/passkey-credential/migration.mjs.map +1 -0
  86. package/esm/models/passkey-credential/passkey-credential.model.d.mts +108 -0
  87. package/esm/models/passkey-credential/passkey-credential.model.d.mts.map +1 -0
  88. package/esm/models/passkey-credential/passkey-credential.model.mjs +70 -0
  89. package/esm/models/passkey-credential/passkey-credential.model.mjs.map +1 -0
  90. package/esm/models/provider-account/index.d.mts +1 -0
  91. package/esm/models/provider-account/index.mjs +3 -0
  92. package/esm/models/provider-account/migration.mjs +25 -0
  93. package/esm/models/provider-account/migration.mjs.map +1 -0
  94. package/esm/models/provider-account/provider-account.model.d.mts +75 -0
  95. package/esm/models/provider-account/provider-account.model.d.mts.map +1 -0
  96. package/esm/models/provider-account/provider-account.model.mjs +55 -0
  97. package/esm/models/provider-account/provider-account.model.mjs.map +1 -0
  98. package/esm/models/refresh-token/refresh-token.model.d.mts +7 -0
  99. package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
  100. package/esm/models/refresh-token/refresh-token.model.mjs +3 -0
  101. package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
  102. package/esm/otp/index.d.mts +1 -0
  103. package/esm/otp/index.mjs +3 -0
  104. package/esm/otp/otp.d.mts +38 -0
  105. package/esm/otp/otp.d.mts.map +1 -0
  106. package/esm/otp/otp.mjs +105 -0
  107. package/esm/otp/otp.mjs.map +1 -0
  108. package/esm/passkeys/index.d.mts +3 -0
  109. package/esm/passkeys/index.mjs +4 -0
  110. package/esm/passkeys/passkey-authentication.d.mts +33 -0
  111. package/esm/passkeys/passkey-authentication.d.mts.map +1 -0
  112. package/esm/passkeys/passkey-authentication.mjs +93 -0
  113. package/esm/passkeys/passkey-authentication.mjs.map +1 -0
  114. package/esm/passkeys/passkey-challenges.mjs +53 -0
  115. package/esm/passkeys/passkey-challenges.mjs.map +1 -0
  116. package/esm/passkeys/passkey-registration.d.mts +29 -0
  117. package/esm/passkeys/passkey-registration.d.mts.map +1 -0
  118. package/esm/passkeys/passkey-registration.mjs +88 -0
  119. package/esm/passkeys/passkey-registration.mjs.map +1 -0
  120. package/esm/passkeys/simplewebauthn.d.mts +21 -0
  121. package/esm/passkeys/simplewebauthn.d.mts.map +1 -0
  122. package/esm/passkeys/simplewebauthn.mjs +13 -0
  123. package/esm/passkeys/simplewebauthn.mjs.map +1 -0
  124. package/esm/providers/google-provider.d.mts +40 -0
  125. package/esm/providers/google-provider.d.mts.map +1 -0
  126. package/esm/providers/google-provider.mjs +105 -0
  127. package/esm/providers/google-provider.mjs.map +1 -0
  128. package/esm/providers/index.d.mts +2 -0
  129. package/esm/providers/index.mjs +4 -0
  130. package/esm/providers/provider-login.d.mts +43 -0
  131. package/esm/providers/provider-login.d.mts.map +1 -0
  132. package/esm/providers/provider-login.mjs +86 -0
  133. package/esm/providers/provider-login.mjs.map +1 -0
  134. package/esm/providers/provider-state-cookie.mjs +63 -0
  135. package/esm/providers/provider-state-cookie.mjs.map +1 -0
  136. package/esm/providers/resolve-provider-user.mjs +45 -0
  137. package/esm/providers/resolve-provider-user.mjs.map +1 -0
  138. package/esm/services/auth-config.mjs +84 -0
  139. package/esm/services/auth-config.mjs.map +1 -1
  140. package/esm/services/auth.service.d.mts +59 -2
  141. package/esm/services/auth.service.d.mts.map +1 -1
  142. package/esm/services/auth.service.mjs +86 -1
  143. package/esm/services/auth.service.mjs.map +1 -1
  144. package/esm/services/current-user.d.mts +21 -0
  145. package/esm/services/current-user.d.mts.map +1 -0
  146. package/esm/services/current-user.mjs +24 -0
  147. package/esm/services/current-user.mjs.map +1 -0
  148. package/esm/services/email-verification.d.mts +35 -0
  149. package/esm/services/email-verification.d.mts.map +1 -0
  150. package/esm/services/email-verification.mjs +55 -0
  151. package/esm/services/email-verification.mjs.map +1 -0
  152. package/esm/services/index.d.mts +5 -1
  153. package/esm/services/index.mjs +4 -0
  154. package/esm/services/one-time-tokens.d.mts +32 -0
  155. package/esm/services/one-time-tokens.d.mts.map +1 -0
  156. package/esm/services/one-time-tokens.mjs +56 -0
  157. package/esm/services/one-time-tokens.mjs.map +1 -0
  158. package/esm/services/optional-peer.mjs +37 -0
  159. package/esm/services/optional-peer.mjs.map +1 -0
  160. package/esm/services/password-reset.d.mts +34 -0
  161. package/esm/services/password-reset.d.mts.map +1 -0
  162. package/esm/services/password-reset.mjs +81 -0
  163. package/esm/services/password-reset.mjs.map +1 -0
  164. package/esm/services/token-notifications.mjs +87 -0
  165. package/esm/services/token-notifications.mjs.map +1 -0
  166. package/esm/utils/auth-error-codes.d.mts +48 -1
  167. package/esm/utils/auth-error-codes.d.mts.map +1 -1
  168. package/esm/utils/auth-error-codes.mjs +41 -0
  169. package/esm/utils/auth-error-codes.mjs.map +1 -1
  170. package/esm/utils/index.mjs +1 -0
  171. package/esm/utils/safe-equal.d.mts +6 -0
  172. package/esm/utils/safe-equal.d.mts.map +1 -0
  173. package/esm/utils/safe-equal.mjs +13 -0
  174. package/esm/utils/safe-equal.mjs.map +1 -0
  175. package/llms-full.txt +402 -29
  176. package/llms.txt +2 -0
  177. package/package.json +21 -7
  178. package/skills/handle-login-and-logout/SKILL.md +1 -1
  179. package/skills/login-with-providers/SKILL.md +154 -0
  180. package/skills/manage-tokens/SKILL.md +7 -1
  181. package/skills/overview/SKILL.md +1 -1
  182. package/skills/protect-routes/SKILL.md +71 -13
  183. package/skills/register-user/SKILL.md +6 -8
  184. package/skills/run-auth-commands/SKILL.md +1 -1
  185. package/skills/verify-email-and-reset-password/SKILL.md +137 -0
@@ -0,0 +1 @@
1
+ import { OneTimeToken, OneTimeTokenPurpose, oneTimeTokenSchema } from "./one-time-token.model.mjs";
@@ -0,0 +1,3 @@
1
+ import { OneTimeToken, oneTimeTokenSchema } from "./one-time-token.model.mjs";
2
+
3
+ export { };
@@ -0,0 +1,31 @@
1
+ import { OneTimeToken } from "./one-time-token.model.mjs";
2
+ import { migrate } from "@warlock.js/cascade";
3
+
4
+ //#region ../auth/src/models/one-time-token/migration.ts
5
+ const OneTimeTokenMigration = migrate(OneTimeToken, {
6
+ name: "oneTimeToken",
7
+ up() {
8
+ this.createTableIfNotExists();
9
+ this.primaryUuid();
10
+ this.string("token_hash", 64).unique();
11
+ this.string("purpose", 32);
12
+ this.foreignId("user_id").nullable().index();
13
+ this.string("user_type", 50).nullable();
14
+ this.timestamp("expires_at").index().nullable();
15
+ this.timestamp("consumed_at").nullable();
16
+ this.integer("attempts").default(0);
17
+ this.index([
18
+ "user_id",
19
+ "user_type",
20
+ "purpose"
21
+ ]);
22
+ this.timestamps();
23
+ },
24
+ down() {
25
+ this.dropTableIfExists();
26
+ }
27
+ });
28
+
29
+ //#endregion
30
+ export { OneTimeTokenMigration };
31
+ //# sourceMappingURL=migration.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.mjs","names":[],"sources":["../../../../../../../../auth/src/models/one-time-token/migration.ts"],"sourcesContent":["import { migrate } from \"@warlock.js/cascade\";\nimport { OneTimeToken } from \"./one-time-token.model\";\n\nexport const OneTimeTokenMigration = migrate(OneTimeToken, {\n name: \"oneTimeToken\",\n up() {\n this.createTableIfNotExists();\n\n this.primaryUuid();\n\n // SHA-256 hex digest of the raw token — the raw token is never stored.\n this.string(\"token_hash\", 64).unique();\n this.string(\"purpose\", 32);\n\n // `foreignId` follows the DataSource's default primary-key type, matching\n // the access/refresh token tables. Nullable only for a passkey\n // authentication challenge, which is issued before anyone is identified.\n this.foreignId(\"user_id\").nullable().index();\n this.string(\"user_type\", 50).nullable();\n\n this.timestamp(\"expires_at\").index().nullable();\n this.timestamp(\"consumed_at\").nullable();\n\n // Failed verify attempts — only OTP codes are capped on it.\n this.integer(\"attempts\").default(0);\n\n // Invalidating a user's previous reset tokens filters on these.\n this.index([\"user_id\", \"user_type\", \"purpose\"]);\n\n this.timestamps();\n },\n down() {\n this.dropTableIfExists();\n },\n});\n"],"mappings":";;;;AAGA,MAAa,wBAAwB,QAAQ,cAAc;CACzD,MAAM;CACN,KAAK;EACH,KAAK,uBAAuB;EAE5B,KAAK,YAAY;EAGjB,KAAK,OAAO,cAAc,EAAE,CAAC,CAAC,OAAO;EACrC,KAAK,OAAO,WAAW,EAAE;EAKzB,KAAK,UAAU,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM;EAC3C,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,SAAS;EAEtC,KAAK,UAAU,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS;EAC9C,KAAK,UAAU,aAAa,CAAC,CAAC,SAAS;EAGvC,KAAK,QAAQ,UAAU,CAAC,CAAC,QAAQ,CAAC;EAGlC,KAAK,MAAM;GAAC;GAAW;GAAa;EAAS,CAAC;EAE9C,KAAK,WAAW;CAClB;CACA,OAAO;EACL,KAAK,kBAAkB;CACzB;AACF,CAAC"}
@@ -0,0 +1,173 @@
1
+ import { Auth } from "../auth.model.mjs";
2
+ import { Model } from "@warlock.js/cascade";
3
+
4
+ //#region ../auth/src/models/one-time-token/one-time-token.model.d.ts
5
+ /** What a one-time token may be used for. A token only ever satisfies its own purpose. */
6
+ type OneTimeTokenPurpose = "email-verification" | "password-reset" | "otp" | "passkey-registration" | "passkey-authentication";
7
+ /**
8
+ * Seal schema for the persisted one-time token. Exported so an override can
9
+ * extend it (e.g. a tenant key).
10
+ */
11
+ declare const oneTimeTokenSchema: import("@warlock.js/seal").ObjectValidator<{
12
+ token_hash: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
13
+ purpose: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
14
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean> & {
15
+ isOptional: true;
16
+ };
17
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
18
+ isOptional: true;
19
+ };
20
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
21
+ consumed_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
22
+ isOptional: true;
23
+ };
24
+ attempts: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
25
+ isOptional: true;
26
+ };
27
+ }> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
28
+ token_hash: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
29
+ purpose: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
30
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean> & {
31
+ isOptional: true;
32
+ };
33
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
34
+ isOptional: true;
35
+ };
36
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
37
+ consumed_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
38
+ isOptional: true;
39
+ };
40
+ attempts: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
41
+ isOptional: true;
42
+ };
43
+ }>, import("@warlock.js/seal").InferInputObjectShape<{
44
+ token_hash: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
45
+ purpose: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
46
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean> & {
47
+ isOptional: true;
48
+ };
49
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
50
+ isOptional: true;
51
+ };
52
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
53
+ consumed_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
54
+ isOptional: true;
55
+ };
56
+ attempts: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
57
+ isOptional: true;
58
+ };
59
+ }>>;
60
+ /**
61
+ * Persisted one-time secret: email-verification / password-reset token, OTP
62
+ * code, or passkey ceremony challenge.
63
+ *
64
+ * **Only a SHA-256 hash is stored** — the raw token exists in the notification
65
+ * and nowhere else, so a leaked table cannot be replayed. Single use is
66
+ * enforced by {@link consumeIfActive}, a conditional update in the same shape
67
+ * as `RefreshToken.revokeIfActive`. Override via `config.auth.oneTimeToken.model`.
68
+ */
69
+ declare class OneTimeToken extends Model {
70
+ static table: string;
71
+ static schema: import("@warlock.js/seal").ObjectValidator<{
72
+ token_hash: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
73
+ purpose: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
74
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean> & {
75
+ isOptional: true;
76
+ };
77
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
78
+ isOptional: true;
79
+ };
80
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
81
+ consumed_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
82
+ isOptional: true;
83
+ };
84
+ attempts: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
85
+ isOptional: true;
86
+ };
87
+ }> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
88
+ token_hash: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
89
+ purpose: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
90
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean> & {
91
+ isOptional: true;
92
+ };
93
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
94
+ isOptional: true;
95
+ };
96
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
97
+ consumed_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
98
+ isOptional: true;
99
+ };
100
+ attempts: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
101
+ isOptional: true;
102
+ };
103
+ }>, import("@warlock.js/seal").InferInputObjectShape<{
104
+ token_hash: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
105
+ purpose: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
106
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean> & {
107
+ isOptional: true;
108
+ };
109
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
110
+ isOptional: true;
111
+ };
112
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
113
+ consumed_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
114
+ isOptional: true;
115
+ };
116
+ attempts: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
117
+ isOptional: true;
118
+ };
119
+ }>>;
120
+ /**
121
+ * Permanent regardless of the data source default — Mongo's driver default
122
+ * is `"trash"`, which would copy spent token hashes into
123
+ * `one_time_tokensTrash` on every `destroy()`. A purged one-time token must
124
+ * not survive anywhere.
125
+ */
126
+ static deleteStrategy: "permanent";
127
+ /** The user this token was issued for. */
128
+ get userId(): any;
129
+ /** The user-type slug this token was issued for. */
130
+ get userType(): string;
131
+ /** Whether the token has been used (or invalidated by a newer one). */
132
+ get isConsumed(): boolean;
133
+ /** Whether the expiry has passed. Fails closed: an unusable `expires_at` counts as expired. */
134
+ get isExpired(): boolean;
135
+ /**
136
+ * Atomically mark this token consumed ONLY if nobody has yet. Resolves `true`
137
+ * for the single caller that won; every concurrent or later caller gets
138
+ * `false`.
139
+ */
140
+ consumeIfActive(): Promise<boolean>;
141
+ /** Persist a new token row. `tokenHash` is the SHA-256 hex digest, never the raw token. */
142
+ static issue(user: Auth, purpose: OneTimeTokenPurpose, tokenHash: string, expiresAt: Date): Promise<OneTimeToken>;
143
+ /**
144
+ * Persist a passkey ceremony challenge. `user` is omitted for an
145
+ * authentication challenge, issued before anyone is identified.
146
+ */
147
+ static issueChallenge(purpose: OneTimeTokenPurpose, tokenHash: string, expiresAt: Date, user?: Auth): Promise<OneTimeToken>;
148
+ /** The user's newest unconsumed token of `purpose` (at most one for OTP). */
149
+ static findActiveFor(user: Auth, purpose: OneTimeTokenPurpose): Promise<OneTimeToken | null>;
150
+ /**
151
+ * Atomically count one verify attempt, ONLY while fewer than `max` have been
152
+ * counted and the token is unconsumed. Resolves `false` once the cap is
153
+ * reached — of N concurrent attempts, at most `max` ever resolve `true`.
154
+ */
155
+ recordAttempt(max: number): Promise<boolean>;
156
+ /**
157
+ * Hard-delete every spent row — expired, or consumed (used, invalidated by a
158
+ * newer token, or exhausted). Neither can ever be redeemed again, so nothing
159
+ * reads them. Hard-deleted, never trashed. Returns the number removed. Runs from the `auth.cleanup` CLI
160
+ * command (a cold batch path).
161
+ */
162
+ static purgeSpent(): Promise<number>;
163
+ /** Find a token by hash, scoped to a purpose — a token of the other purpose is not found. */
164
+ static findByHash(tokenHash: string, purpose: OneTimeTokenPurpose): Promise<OneTimeToken | null>;
165
+ /**
166
+ * Consume every still-unused token of `purpose` for the user, returning how
167
+ * many were invalidated. Used before issuing a new password-reset token.
168
+ */
169
+ static invalidateActiveFor(user: Auth, purpose: OneTimeTokenPurpose): Promise<number>;
170
+ }
171
+ //#endregion
172
+ export { OneTimeToken, OneTimeTokenPurpose, oneTimeTokenSchema };
173
+ //# sourceMappingURL=one-time-token.model.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"one-time-token.model.d.mts","names":[],"sources":["../../../../../../../../auth/src/models/one-time-token/one-time-token.model.ts"],"mappings":";;;;;KAMY,mBAAA;AAAZ;;;;AAAA,cAWa,kBAAA,6BAAkB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoBlB,YAAA,SAAqB,KAAA;EAAA,OAClB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAHtB;;;;SAWgB,cAAA;;MAGH,MAAA;;MAKA,QAAA;;MAKA,UAAA;;MAKA,SAAA;;;;;;EAaE,eAAA,IAAmB,OAAA;EAgBf;EAAA,OAJH,KAAA,CACZ,IAAA,EAAM,IAAA,EACN,OAAA,EAAS,mBAAA,EACT,SAAA,UACA,SAAA,EAAW,IAAA,GAAI,OAAA,CAAA,YAAA;EAkBN;;;;EAAA,OADG,cAAA,CACZ,OAAA,EAAS,mBAAA,EACT,SAAA,UACA,SAAA,EAAW,IAAA,EACX,IAAA,GAAO,IAAA,GAAI,OAAA,CAAA,YAAA;EAeL;EAAA,OADM,aAAA,CACZ,IAAA,EAAM,IAAA,EACN,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,YAAA;EAAA;;;;;EAYE,aAAA,CAAc,GAAA,WAAc,OAAA;EAqCtC;;;;;;EAAA,OAlBiB,UAAA,IAAc,OAAA;EA/HF;EAAA,OA8IlB,UAAA,CACZ,SAAA,UACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,YAAA;EA9IG;;;;EAAA,OAsJA,mBAAA,CAAoB,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,mBAAA,GAAsB,OAAA;AAAA"}
@@ -0,0 +1,151 @@
1
+ import { isUsableExpiry } from "../../utils/token-expiry.mjs";
2
+ import { Model } from "@warlock.js/cascade";
3
+ import { v } from "@warlock.js/seal";
4
+
5
+ //#region ../auth/src/models/one-time-token/one-time-token.model.ts
6
+ /**
7
+ * Seal schema for the persisted one-time token. Exported so an override can
8
+ * extend it (e.g. a tenant key).
9
+ */
10
+ const oneTimeTokenSchema = v.object({
11
+ token_hash: v.string().required(),
12
+ purpose: v.string().required(),
13
+ user_id: v.scalar().optional(),
14
+ user_type: v.string().optional(),
15
+ expires_at: v.date().required(),
16
+ consumed_at: v.date().optional(),
17
+ attempts: v.int().optional()
18
+ });
19
+ /**
20
+ * Persisted one-time secret: email-verification / password-reset token, OTP
21
+ * code, or passkey ceremony challenge.
22
+ *
23
+ * **Only a SHA-256 hash is stored** — the raw token exists in the notification
24
+ * and nowhere else, so a leaked table cannot be replayed. Single use is
25
+ * enforced by {@link consumeIfActive}, a conditional update in the same shape
26
+ * as `RefreshToken.revokeIfActive`. Override via `config.auth.oneTimeToken.model`.
27
+ */
28
+ var OneTimeToken = class extends Model {
29
+ static {
30
+ this.table = "one_time_tokens";
31
+ }
32
+ static {
33
+ this.schema = oneTimeTokenSchema;
34
+ }
35
+ static {
36
+ this.deleteStrategy = "permanent";
37
+ }
38
+ /** The user this token was issued for. */
39
+ get userId() {
40
+ return this.get("user_id");
41
+ }
42
+ /** The user-type slug this token was issued for. */
43
+ get userType() {
44
+ return this.get("user_type");
45
+ }
46
+ /** Whether the token has been used (or invalidated by a newer one). */
47
+ get isConsumed() {
48
+ return !!this.get("consumed_at");
49
+ }
50
+ /** Whether the expiry has passed. Fails closed: an unusable `expires_at` counts as expired. */
51
+ get isExpired() {
52
+ const expiresAt = this.get("expires_at");
53
+ if (!isUsableExpiry(expiresAt)) return true;
54
+ return new Date(expiresAt).getTime() <= Date.now();
55
+ }
56
+ /**
57
+ * Atomically mark this token consumed ONLY if nobody has yet. Resolves `true`
58
+ * for the single caller that won; every concurrent or later caller gets
59
+ * `false`.
60
+ */
61
+ async consumeIfActive() {
62
+ return await this.constructor.atomic({
63
+ id: this.id,
64
+ consumed_at: null
65
+ }, { $set: { consumed_at: /* @__PURE__ */ new Date() } }) > 0;
66
+ }
67
+ /** Persist a new token row. `tokenHash` is the SHA-256 hex digest, never the raw token. */
68
+ static issue(user, purpose, tokenHash, expiresAt) {
69
+ return this.create({
70
+ token_hash: tokenHash,
71
+ purpose,
72
+ user_id: user.id,
73
+ user_type: user.userType,
74
+ expires_at: expiresAt,
75
+ consumed_at: null,
76
+ attempts: 0
77
+ });
78
+ }
79
+ /**
80
+ * Persist a passkey ceremony challenge. `user` is omitted for an
81
+ * authentication challenge, issued before anyone is identified.
82
+ */
83
+ static issueChallenge(purpose, tokenHash, expiresAt, user) {
84
+ return this.create({
85
+ token_hash: tokenHash,
86
+ purpose,
87
+ user_id: user?.id ?? null,
88
+ user_type: user?.userType ?? null,
89
+ expires_at: expiresAt,
90
+ consumed_at: null,
91
+ attempts: 0
92
+ });
93
+ }
94
+ /** The user's newest unconsumed token of `purpose` (at most one for OTP). */
95
+ static findActiveFor(user, purpose) {
96
+ return this.query().where({
97
+ user_id: user.id,
98
+ user_type: user.userType,
99
+ purpose,
100
+ consumed_at: null
101
+ }).orderBy("created_at", "desc").first();
102
+ }
103
+ /**
104
+ * Atomically count one verify attempt, ONLY while fewer than `max` have been
105
+ * counted and the token is unconsumed. Resolves `false` once the cap is
106
+ * reached — of N concurrent attempts, at most `max` ever resolve `true`.
107
+ */
108
+ async recordAttempt(max) {
109
+ return await this.constructor.atomic({
110
+ id: this.id,
111
+ consumed_at: null,
112
+ attempts: { $lt: max }
113
+ }, { $inc: { attempts: 1 } }, { trustedFilter: true }) > 0;
114
+ }
115
+ /**
116
+ * Hard-delete every spent row — expired, or consumed (used, invalidated by a
117
+ * newer token, or exhausted). Neither can ever be redeemed again, so nothing
118
+ * reads them. Hard-deleted, never trashed. Returns the number removed. Runs from the `auth.cleanup` CLI
119
+ * command (a cold batch path).
120
+ */
121
+ static async purgeSpent() {
122
+ const expired = await this.query().where("expires_at", "<", /* @__PURE__ */ new Date()).get();
123
+ const consumed = await this.query().whereNotNull("consumed_at").get();
124
+ const spent = new Map([...expired, ...consumed].map((token) => [String(token.id), token]));
125
+ for (const token of spent.values()) await token.destroy();
126
+ return spent.size;
127
+ }
128
+ /** Find a token by hash, scoped to a purpose — a token of the other purpose is not found. */
129
+ static findByHash(tokenHash, purpose) {
130
+ return this.first({
131
+ token_hash: tokenHash,
132
+ purpose
133
+ });
134
+ }
135
+ /**
136
+ * Consume every still-unused token of `purpose` for the user, returning how
137
+ * many were invalidated. Used before issuing a new password-reset token.
138
+ */
139
+ static invalidateActiveFor(user, purpose) {
140
+ return this.atomic({
141
+ user_id: user.id,
142
+ user_type: user.userType,
143
+ purpose,
144
+ consumed_at: null
145
+ }, { $set: { consumed_at: /* @__PURE__ */ new Date() } });
146
+ }
147
+ };
148
+
149
+ //#endregion
150
+ export { OneTimeToken, oneTimeTokenSchema };
151
+ //# sourceMappingURL=one-time-token.model.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"one-time-token.model.mjs","names":[],"sources":["../../../../../../../../auth/src/models/one-time-token/one-time-token.model.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\nimport { v } from \"@warlock.js/seal\";\nimport { isUsableExpiry } from \"../../utils/token-expiry\";\nimport type { Auth } from \"../auth.model\";\n\n/** What a one-time token may be used for. A token only ever satisfies its own purpose. */\nexport type OneTimeTokenPurpose =\n | \"email-verification\"\n | \"password-reset\"\n | \"otp\"\n | \"passkey-registration\"\n | \"passkey-authentication\";\n\n/**\n * Seal schema for the persisted one-time token. Exported so an override can\n * extend it (e.g. a tenant key).\n */\nexport const oneTimeTokenSchema = v.object({\n token_hash: v.string().required(),\n purpose: v.string().required(),\n // Absent only on a passkey authentication challenge (no user identified yet).\n user_id: v.scalar().optional(),\n user_type: v.string().optional(),\n expires_at: v.date().required(),\n consumed_at: v.date().optional(),\n attempts: v.int().optional(),\n});\n\n/**\n * Persisted one-time secret: email-verification / password-reset token, OTP\n * code, or passkey ceremony challenge.\n *\n * **Only a SHA-256 hash is stored** — the raw token exists in the notification\n * and nowhere else, so a leaked table cannot be replayed. Single use is\n * enforced by {@link consumeIfActive}, a conditional update in the same shape\n * as `RefreshToken.revokeIfActive`. Override via `config.auth.oneTimeToken.model`.\n */\nexport class OneTimeToken extends Model {\n public static table = \"one_time_tokens\";\n\n public static schema = oneTimeTokenSchema;\n\n /**\n * Permanent regardless of the data source default — Mongo's driver default\n * is `\"trash\"`, which would copy spent token hashes into\n * `one_time_tokensTrash` on every `destroy()`. A purged one-time token must\n * not survive anywhere.\n */\n public static deleteStrategy = \"permanent\" as const;\n\n /** The user this token was issued for. */\n public get userId() {\n return this.get(\"user_id\");\n }\n\n /** The user-type slug this token was issued for. */\n public get userType(): string {\n return this.get(\"user_type\");\n }\n\n /** Whether the token has been used (or invalidated by a newer one). */\n public get isConsumed(): boolean {\n return !!this.get(\"consumed_at\");\n }\n\n /** Whether the expiry has passed. Fails closed: an unusable `expires_at` counts as expired. */\n public get isExpired(): boolean {\n const expiresAt = this.get(\"expires_at\");\n\n if (!isUsableExpiry(expiresAt)) return true;\n\n return new Date(expiresAt).getTime() <= Date.now();\n }\n\n /**\n * Atomically mark this token consumed ONLY if nobody has yet. Resolves `true`\n * for the single caller that won; every concurrent or later caller gets\n * `false`.\n */\n public async consumeIfActive(): Promise<boolean> {\n const modelClass = this.constructor as typeof OneTimeToken;\n\n const consumed = await modelClass.atomic(\n { id: this.id, consumed_at: null },\n { $set: { consumed_at: new Date() } },\n );\n\n return consumed > 0;\n }\n\n /** Persist a new token row. `tokenHash` is the SHA-256 hex digest, never the raw token. */\n public static issue(\n user: Auth,\n purpose: OneTimeTokenPurpose,\n tokenHash: string,\n expiresAt: Date,\n ) {\n return this.create({\n token_hash: tokenHash,\n purpose,\n user_id: user.id,\n user_type: user.userType,\n expires_at: expiresAt,\n consumed_at: null,\n attempts: 0,\n });\n }\n\n /**\n * Persist a passkey ceremony challenge. `user` is omitted for an\n * authentication challenge, issued before anyone is identified.\n */\n public static issueChallenge(\n purpose: OneTimeTokenPurpose,\n tokenHash: string,\n expiresAt: Date,\n user?: Auth,\n ) {\n return this.create({\n token_hash: tokenHash,\n purpose,\n user_id: user?.id ?? null,\n user_type: user?.userType ?? null,\n expires_at: expiresAt,\n consumed_at: null,\n attempts: 0,\n });\n }\n\n /** The user's newest unconsumed token of `purpose` (at most one for OTP). */\n public static findActiveFor(\n user: Auth,\n purpose: OneTimeTokenPurpose,\n ): Promise<OneTimeToken | null> {\n return this.query()\n .where({ user_id: user.id, user_type: user.userType, purpose, consumed_at: null })\n .orderBy(\"created_at\", \"desc\")\n .first();\n }\n\n /**\n * Atomically count one verify attempt, ONLY while fewer than `max` have been\n * counted and the token is unconsumed. Resolves `false` once the cap is\n * reached — of N concurrent attempts, at most `max` ever resolve `true`.\n */\n public async recordAttempt(max: number): Promise<boolean> {\n const modelClass = this.constructor as typeof OneTimeToken;\n\n const counted = await modelClass.atomic(\n { id: this.id, consumed_at: null, attempts: { $lt: max } },\n { $inc: { attempts: 1 } },\n // Code-authored filter; nothing here comes from the request.\n { trustedFilter: true },\n );\n\n return counted > 0;\n }\n\n /**\n * Hard-delete every spent row — expired, or consumed (used, invalidated by a\n * newer token, or exhausted). Neither can ever be redeemed again, so nothing\n * reads them. Hard-deleted, never trashed. Returns the number removed. Runs from the `auth.cleanup` CLI\n * command (a cold batch path).\n */\n public static async purgeSpent(): Promise<number> {\n const expired = await this.query().where(\"expires_at\", \"<\", new Date()).get();\n const consumed = await this.query().whereNotNull(\"consumed_at\").get();\n const spent = new Map([...expired, ...consumed].map((token) => [String(token.id), token]));\n\n for (const token of spent.values()) {\n // `static deleteStrategy` above already forces \"permanent\" regardless\n // of the data source default (Mongo's is \"trash\").\n await token.destroy();\n }\n\n return spent.size;\n }\n\n /** Find a token by hash, scoped to a purpose — a token of the other purpose is not found. */\n public static findByHash(\n tokenHash: string,\n purpose: OneTimeTokenPurpose,\n ): Promise<OneTimeToken | null> {\n return this.first({ token_hash: tokenHash, purpose });\n }\n\n /**\n * Consume every still-unused token of `purpose` for the user, returning how\n * many were invalidated. Used before issuing a new password-reset token.\n */\n public static invalidateActiveFor(user: Auth, purpose: OneTimeTokenPurpose): Promise<number> {\n return this.atomic(\n { user_id: user.id, user_type: user.userType, purpose, consumed_at: null },\n { $set: { consumed_at: new Date() } },\n );\n }\n}\n"],"mappings":";;;;;;;;;AAiBA,MAAa,qBAAqB,EAAE,OAAO;CACzC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAE7B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,YAAY,EAAE,KAAK,CAAC,CAAC,SAAS;CAC9B,aAAa,EAAE,KAAK,CAAC,CAAC,SAAS;CAC/B,UAAU,EAAE,IAAI,CAAC,CAAC,SAAS;AAC7B,CAAC;;;;;;;;;;AAWD,IAAa,eAAb,cAAkC,MAAM;;eAChB;;;gBAEC;;;wBAQQ;;;CAG/B,IAAW,SAAS;EAClB,OAAO,KAAK,IAAI,SAAS;CAC3B;;CAGA,IAAW,WAAmB;EAC5B,OAAO,KAAK,IAAI,WAAW;CAC7B;;CAGA,IAAW,aAAsB;EAC/B,OAAO,CAAC,CAAC,KAAK,IAAI,aAAa;CACjC;;CAGA,IAAW,YAAqB;EAC9B,MAAM,YAAY,KAAK,IAAI,YAAY;EAEvC,IAAI,CAAC,eAAe,SAAS,GAAG,OAAO;EAEvC,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,QAAQ,KAAK,KAAK,IAAI;CACnD;;;;;;CAOA,MAAa,kBAAoC;EAQ/C,OAAO,MAPY,KAAK,YAEU,OAChC;GAAE,IAAI,KAAK;GAAI,aAAa;EAAK,GACjC,EAAE,MAAM,EAAE,6BAAa,IAAI,KAAK,EAAE,EAAE,CACtC,IAEkB;CACpB;;CAGA,OAAc,MACZ,MACA,SACA,WACA,WACA;EACA,OAAO,KAAK,OAAO;GACjB,YAAY;GACZ;GACA,SAAS,KAAK;GACd,WAAW,KAAK;GAChB,YAAY;GACZ,aAAa;GACb,UAAU;EACZ,CAAC;CACH;;;;;CAMA,OAAc,eACZ,SACA,WACA,WACA,MACA;EACA,OAAO,KAAK,OAAO;GACjB,YAAY;GACZ;GACA,SAAS,MAAM,MAAM;GACrB,WAAW,MAAM,YAAY;GAC7B,YAAY;GACZ,aAAa;GACb,UAAU;EACZ,CAAC;CACH;;CAGA,OAAc,cACZ,MACA,SAC8B;EAC9B,OAAO,KAAK,MAAM,CAAC,CAChB,MAAM;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;GAAU;GAAS,aAAa;EAAK,CAAC,CAAC,CACjF,QAAQ,cAAc,MAAM,CAAC,CAC7B,MAAM;CACX;;;;;;CAOA,MAAa,cAAc,KAA+B;EAUxD,OAAO,MATY,KAAK,YAES,OAC/B;GAAE,IAAI,KAAK;GAAI,aAAa;GAAM,UAAU,EAAE,KAAK,IAAI;EAAE,GACzD,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,GAExB,EAAE,eAAe,KAAK,CACxB,IAEiB;CACnB;;;;;;;CAQA,aAAoB,aAA8B;EAChD,MAAM,UAAU,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,cAAc,qBAAK,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI;EAC5E,MAAM,WAAW,MAAM,KAAK,MAAM,CAAC,CAAC,aAAa,aAAa,CAAC,CAAC,IAAI;EACpE,MAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC,KAAK,UAAU,CAAC,OAAO,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC;EAEzF,KAAK,MAAM,SAAS,MAAM,OAAO,GAG/B,MAAM,MAAM,QAAQ;EAGtB,OAAO,MAAM;CACf;;CAGA,OAAc,WACZ,WACA,SAC8B;EAC9B,OAAO,KAAK,MAAM;GAAE,YAAY;GAAW;EAAQ,CAAC;CACtD;;;;;CAMA,OAAc,oBAAoB,MAAY,SAA+C;EAC3F,OAAO,KAAK,OACV;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;GAAU;GAAS,aAAa;EAAK,GACzE,EAAE,MAAM,EAAE,6BAAa,IAAI,KAAK,EAAE,EAAE,CACtC;CACF;AACF"}
@@ -0,0 +1 @@
1
+ import { PasskeyCredential, passkeyCredentialSchema } from "./passkey-credential.model.mjs";
@@ -0,0 +1,3 @@
1
+ import { PasskeyCredential, passkeyCredentialSchema } from "./passkey-credential.model.mjs";
2
+
3
+ export { };
@@ -0,0 +1,25 @@
1
+ import { PasskeyCredential } from "./passkey-credential.model.mjs";
2
+ import { migrate } from "@warlock.js/cascade";
3
+
4
+ //#region ../auth/src/models/passkey-credential/migration.ts
5
+ const PasskeyCredentialMigration = migrate(PasskeyCredential, {
6
+ name: "passkeyCredential",
7
+ up() {
8
+ this.createTableIfNotExists();
9
+ this.primaryUuid();
10
+ this.text("credential_id").unique();
11
+ this.text("public_key");
12
+ this.bigInteger("counter").default(0);
13
+ this.json("transports").nullable();
14
+ this.foreignId("user_id").index();
15
+ this.string("user_type", 50).nullable();
16
+ this.timestamps();
17
+ },
18
+ down() {
19
+ this.dropTableIfExists();
20
+ }
21
+ });
22
+
23
+ //#endregion
24
+ export { PasskeyCredentialMigration };
25
+ //# sourceMappingURL=migration.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.mjs","names":[],"sources":["../../../../../../../../auth/src/models/passkey-credential/migration.ts"],"sourcesContent":["import { migrate } from \"@warlock.js/cascade\";\nimport { PasskeyCredential } from \"./passkey-credential.model\";\n\nexport const PasskeyCredentialMigration = migrate(PasskeyCredential, {\n name: \"passkeyCredential\",\n up() {\n this.createTableIfNotExists();\n\n this.primaryUuid();\n\n this.text(\"credential_id\").unique();\n this.text(\"public_key\");\n this.bigInteger(\"counter\").default(0);\n this.json(\"transports\").nullable();\n this.foreignId(\"user_id\").index();\n this.string(\"user_type\", 50).nullable();\n\n this.timestamps();\n },\n down() {\n this.dropTableIfExists();\n },\n});\n"],"mappings":";;;;AAGA,MAAa,6BAA6B,QAAQ,mBAAmB;CACnE,MAAM;CACN,KAAK;EACH,KAAK,uBAAuB;EAE5B,KAAK,YAAY;EAEjB,KAAK,KAAK,eAAe,CAAC,CAAC,OAAO;EAClC,KAAK,KAAK,YAAY;EACtB,KAAK,WAAW,SAAS,CAAC,CAAC,QAAQ,CAAC;EACpC,KAAK,KAAK,YAAY,CAAC,CAAC,SAAS;EACjC,KAAK,UAAU,SAAS,CAAC,CAAC,MAAM;EAChC,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,SAAS;EAEtC,KAAK,WAAW;CAClB;CACA,OAAO;EACL,KAAK,kBAAkB;CACzB;AACF,CAAC"}
@@ -0,0 +1,108 @@
1
+ import { Auth } from "../auth.model.mjs";
2
+ import { Model } from "@warlock.js/cascade";
3
+
4
+ //#region ../auth/src/models/passkey-credential/passkey-credential.model.d.ts
5
+ /** Seal schema for a stored passkey. Exported so an override can extend it. */
6
+ declare const passkeyCredentialSchema: import("@warlock.js/seal").ObjectValidator<{
7
+ credential_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
8
+ public_key: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
9
+ counter: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number>;
10
+ transports: import("@warlock.js/seal").ArrayValidator & {
11
+ validator: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
12
+ } & import("@warlock.js/seal").StandardSchemaV1<string[], string[]> & {
13
+ isOptional: true;
14
+ };
15
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
16
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
17
+ }> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
18
+ credential_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
19
+ public_key: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
20
+ counter: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number>;
21
+ transports: import("@warlock.js/seal").ArrayValidator & {
22
+ validator: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
23
+ } & import("@warlock.js/seal").StandardSchemaV1<string[], string[]> & {
24
+ isOptional: true;
25
+ };
26
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
27
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
28
+ }>, import("@warlock.js/seal").InferInputObjectShape<{
29
+ credential_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
30
+ public_key: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
31
+ counter: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number>;
32
+ transports: import("@warlock.js/seal").ArrayValidator & {
33
+ validator: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
34
+ } & import("@warlock.js/seal").StandardSchemaV1<string[], string[]> & {
35
+ isOptional: true;
36
+ };
37
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
38
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
39
+ }>>;
40
+ /**
41
+ * A registered WebAuthn credential. `credential_id` and `public_key` are
42
+ * base64url; `counter` is the last signature counter seen, which an
43
+ * authentication must advance (a clone detector).
44
+ */
45
+ declare class PasskeyCredential extends Model {
46
+ static table: string;
47
+ static schema: import("@warlock.js/seal").ObjectValidator<{
48
+ credential_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
49
+ public_key: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
50
+ counter: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number>;
51
+ transports: import("@warlock.js/seal").ArrayValidator & {
52
+ validator: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
53
+ } & import("@warlock.js/seal").StandardSchemaV1<string[], string[]> & {
54
+ isOptional: true;
55
+ };
56
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
57
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
58
+ }> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
59
+ credential_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
60
+ public_key: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
61
+ counter: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number>;
62
+ transports: import("@warlock.js/seal").ArrayValidator & {
63
+ validator: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
64
+ } & import("@warlock.js/seal").StandardSchemaV1<string[], string[]> & {
65
+ isOptional: true;
66
+ };
67
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
68
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
69
+ }>, import("@warlock.js/seal").InferInputObjectShape<{
70
+ credential_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
71
+ public_key: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
72
+ counter: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number>;
73
+ transports: import("@warlock.js/seal").ArrayValidator & {
74
+ validator: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
75
+ } & import("@warlock.js/seal").StandardSchemaV1<string[], string[]> & {
76
+ isOptional: true;
77
+ };
78
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
79
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
80
+ }>>;
81
+ /**
82
+ * Permanent regardless of the data source default — Mongo's driver default
83
+ * is `"trash"`. No call site deletes a credential today, but the public key
84
+ * is credential material, so a future `destroy()` here must not leave a
85
+ * copy in `passkey_credentialsTrash`.
86
+ */
87
+ static deleteStrategy: "permanent";
88
+ /** base64url credential id. */
89
+ get credentialId(): string;
90
+ /** Last signature counter seen. */
91
+ get counter(): number;
92
+ /** The owner's id. */
93
+ get userId(): any;
94
+ /** The owner's type slug. */
95
+ get userType(): string;
96
+ /** Find a credential by its base64url id. */
97
+ static findByCredentialId(credentialId: string): Promise<PasskeyCredential | null>;
98
+ /** Every credential the user registered. */
99
+ static listFor(user: Auth): Promise<PasskeyCredential[]>;
100
+ /**
101
+ * Store `newCounter` ONLY if the row still holds `expected` — a compare-and-set,
102
+ * so two concurrent assertions with the same counter cannot both succeed.
103
+ */
104
+ advanceCounter(expected: number, newCounter: number): Promise<boolean>;
105
+ }
106
+ //#endregion
107
+ export { PasskeyCredential, passkeyCredentialSchema };
108
+ //# sourceMappingURL=passkey-credential.model.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passkey-credential.model.d.mts","names":[],"sources":["../../../../../../../../auth/src/models/passkey-credential/passkey-credential.model.ts"],"mappings":";;;;;cAKa,uBAAA,6BAAuB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAcvB,iBAAA,SAA0B,KAAA;EAAA,OACvB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAHtB;;;;SAWgB,cAAA;EA4Bc;EAAA,IAzBjB,YAAA;EAyBwB;EAAA,IApBxB,OAAA;EAnB0B;EAAA,IAwB1B,MAAA;EAxB+B;EAAA,IA6B/B,QAAA;EA5BG;EAAA,OAiCA,kBAAA,CAAmB,YAAA,WAAuB,OAAA,CAAQ,iBAAA;EA/B5C;EAAA,OAoCN,OAAA,CAAQ,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,iBAAA;;;;;EAQ9B,cAAA,CAAe,QAAA,UAAkB,UAAA,WAAqB,OAAA;AAAA"}
@@ -0,0 +1,70 @@
1
+ import { Model } from "@warlock.js/cascade";
2
+ import { v } from "@warlock.js/seal";
3
+
4
+ //#region ../auth/src/models/passkey-credential/passkey-credential.model.ts
5
+ /** Seal schema for a stored passkey. Exported so an override can extend it. */
6
+ const passkeyCredentialSchema = v.object({
7
+ credential_id: v.string().required(),
8
+ public_key: v.string().required(),
9
+ counter: v.int().required(),
10
+ transports: v.array(v.string()).optional(),
11
+ user_id: v.scalar().required(),
12
+ user_type: v.string().required()
13
+ });
14
+ /**
15
+ * A registered WebAuthn credential. `credential_id` and `public_key` are
16
+ * base64url; `counter` is the last signature counter seen, which an
17
+ * authentication must advance (a clone detector).
18
+ */
19
+ var PasskeyCredential = class extends Model {
20
+ static {
21
+ this.table = "passkey_credentials";
22
+ }
23
+ static {
24
+ this.schema = passkeyCredentialSchema;
25
+ }
26
+ static {
27
+ this.deleteStrategy = "permanent";
28
+ }
29
+ /** base64url credential id. */
30
+ get credentialId() {
31
+ return this.get("credential_id");
32
+ }
33
+ /** Last signature counter seen. */
34
+ get counter() {
35
+ return Number(this.get("counter") ?? 0);
36
+ }
37
+ /** The owner's id. */
38
+ get userId() {
39
+ return this.get("user_id");
40
+ }
41
+ /** The owner's type slug. */
42
+ get userType() {
43
+ return this.get("user_type");
44
+ }
45
+ /** Find a credential by its base64url id. */
46
+ static findByCredentialId(credentialId) {
47
+ return this.first({ credential_id: credentialId });
48
+ }
49
+ /** Every credential the user registered. */
50
+ static listFor(user) {
51
+ return this.query().where({
52
+ user_id: user.id,
53
+ user_type: user.userType
54
+ }).get();
55
+ }
56
+ /**
57
+ * Store `newCounter` ONLY if the row still holds `expected` — a compare-and-set,
58
+ * so two concurrent assertions with the same counter cannot both succeed.
59
+ */
60
+ async advanceCounter(expected, newCounter) {
61
+ return await this.constructor.atomic({
62
+ id: this.id,
63
+ counter: expected
64
+ }, { $set: { counter: newCounter } }) > 0;
65
+ }
66
+ };
67
+
68
+ //#endregion
69
+ export { PasskeyCredential, passkeyCredentialSchema };
70
+ //# sourceMappingURL=passkey-credential.model.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passkey-credential.model.mjs","names":[],"sources":["../../../../../../../../auth/src/models/passkey-credential/passkey-credential.model.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\nimport { v } from \"@warlock.js/seal\";\nimport type { Auth } from \"../auth.model\";\n\n/** Seal schema for a stored passkey. Exported so an override can extend it. */\nexport const passkeyCredentialSchema = v.object({\n credential_id: v.string().required(),\n public_key: v.string().required(),\n counter: v.int().required(),\n transports: v.array(v.string()).optional(),\n user_id: v.scalar().required(),\n user_type: v.string().required(),\n});\n\n/**\n * A registered WebAuthn credential. `credential_id` and `public_key` are\n * base64url; `counter` is the last signature counter seen, which an\n * authentication must advance (a clone detector).\n */\nexport class PasskeyCredential extends Model {\n public static table = \"passkey_credentials\";\n\n public static schema = passkeyCredentialSchema;\n\n /**\n * Permanent regardless of the data source default — Mongo's driver default\n * is `\"trash\"`. No call site deletes a credential today, but the public key\n * is credential material, so a future `destroy()` here must not leave a\n * copy in `passkey_credentialsTrash`.\n */\n public static deleteStrategy = \"permanent\" as const;\n\n /** base64url credential id. */\n public get credentialId(): string {\n return this.get(\"credential_id\");\n }\n\n /** Last signature counter seen. */\n public get counter(): number {\n return Number(this.get(\"counter\") ?? 0);\n }\n\n /** The owner's id. */\n public get userId() {\n return this.get(\"user_id\");\n }\n\n /** The owner's type slug. */\n public get userType(): string {\n return this.get(\"user_type\");\n }\n\n /** Find a credential by its base64url id. */\n public static findByCredentialId(credentialId: string): Promise<PasskeyCredential | null> {\n return this.first({ credential_id: credentialId });\n }\n\n /** Every credential the user registered. */\n public static listFor(user: Auth): Promise<PasskeyCredential[]> {\n return this.query().where({ user_id: user.id, user_type: user.userType }).get();\n }\n\n /**\n * Store `newCounter` ONLY if the row still holds `expected` — a compare-and-set,\n * so two concurrent assertions with the same counter cannot both succeed.\n */\n public async advanceCounter(expected: number, newCounter: number): Promise<boolean> {\n const modelClass = this.constructor as typeof PasskeyCredential;\n\n const updated = await modelClass.atomic(\n { id: this.id, counter: expected },\n { $set: { counter: newCounter } },\n );\n\n return updated > 0;\n }\n}\n"],"mappings":";;;;;AAKA,MAAa,0BAA0B,EAAE,OAAO;CAC9C,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS;CACnC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,SAAS,EAAE,IAAI,CAAC,CAAC,SAAS;CAC1B,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;CACzC,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;AACjC,CAAC;;;;;;AAOD,IAAa,oBAAb,cAAuC,MAAM;;eACrB;;;gBAEC;;;wBAQQ;;;CAG/B,IAAW,eAAuB;EAChC,OAAO,KAAK,IAAI,eAAe;CACjC;;CAGA,IAAW,UAAkB;EAC3B,OAAO,OAAO,KAAK,IAAI,SAAS,KAAK,CAAC;CACxC;;CAGA,IAAW,SAAS;EAClB,OAAO,KAAK,IAAI,SAAS;CAC3B;;CAGA,IAAW,WAAmB;EAC5B,OAAO,KAAK,IAAI,WAAW;CAC7B;;CAGA,OAAc,mBAAmB,cAAyD;EACxF,OAAO,KAAK,MAAM,EAAE,eAAe,aAAa,CAAC;CACnD;;CAGA,OAAc,QAAQ,MAA0C;EAC9D,OAAO,KAAK,MAAM,CAAC,CAAC,MAAM;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;EAAS,CAAC,CAAC,CAAC,IAAI;CAChF;;;;;CAMA,MAAa,eAAe,UAAkB,YAAsC;EAQlF,OAAO,MAPY,KAAK,YAES,OAC/B;GAAE,IAAI,KAAK;GAAI,SAAS;EAAS,GACjC,EAAE,MAAM,EAAE,SAAS,WAAW,EAAE,CAClC,IAEiB;CACnB;AACF"}
@@ -0,0 +1 @@
1
+ import { ProviderAccount, providerAccountSchema } from "./provider-account.model.mjs";
@@ -0,0 +1,3 @@
1
+ import { ProviderAccount, providerAccountSchema } from "./provider-account.model.mjs";
2
+
3
+ export { };