@warlock.js/auth 5.12.0 → 5.14.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 (169) hide show
  1. package/CHANGELOG.md +30 -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 +2 -1
  6. package/esm/contracts/providers.d.mts +101 -0
  7. package/esm/contracts/providers.d.mts.map +1 -0
  8. package/esm/contracts/types.d.mts +94 -1
  9. package/esm/contracts/types.d.mts.map +1 -1
  10. package/esm/contracts/types.mjs.map +1 -1
  11. package/esm/errors/auth-provider-sdk-missing.error.d.mts +17 -0
  12. package/esm/errors/auth-provider-sdk-missing.error.d.mts.map +1 -0
  13. package/esm/errors/auth-provider-sdk-missing.error.mjs +22 -0
  14. package/esm/errors/auth-provider-sdk-missing.error.mjs.map +1 -0
  15. package/esm/errors/email-not-verified.error.d.mts +16 -0
  16. package/esm/errors/email-not-verified.error.d.mts.map +1 -0
  17. package/esm/errors/email-not-verified.error.mjs +20 -0
  18. package/esm/errors/email-not-verified.error.mjs.map +1 -0
  19. package/esm/errors/index.d.mts +7 -0
  20. package/esm/errors/index.mjs +9 -0
  21. package/esm/errors/invalid-one-time-token.error.d.mts +17 -0
  22. package/esm/errors/invalid-one-time-token.error.d.mts.map +1 -0
  23. package/esm/errors/invalid-one-time-token.error.mjs +21 -0
  24. package/esm/errors/invalid-one-time-token.error.mjs.map +1 -0
  25. package/esm/errors/invalid-passkey.error.d.mts +18 -0
  26. package/esm/errors/invalid-passkey.error.d.mts.map +1 -0
  27. package/esm/errors/invalid-passkey.error.mjs +23 -0
  28. package/esm/errors/invalid-passkey.error.mjs.map +1 -0
  29. package/esm/errors/invalid-provider-callback.error.d.mts +18 -0
  30. package/esm/errors/invalid-provider-callback.error.d.mts.map +1 -0
  31. package/esm/errors/invalid-provider-callback.error.mjs +23 -0
  32. package/esm/errors/invalid-provider-callback.error.mjs.map +1 -0
  33. package/esm/errors/notifications-unavailable.error.d.mts +15 -0
  34. package/esm/errors/notifications-unavailable.error.d.mts.map +1 -0
  35. package/esm/errors/notifications-unavailable.error.mjs +20 -0
  36. package/esm/errors/notifications-unavailable.error.mjs.map +1 -0
  37. package/esm/errors/provider-email-not-verified.error.d.mts +16 -0
  38. package/esm/errors/provider-email-not-verified.error.d.mts.map +1 -0
  39. package/esm/errors/provider-email-not-verified.error.mjs +20 -0
  40. package/esm/errors/provider-email-not-verified.error.mjs.map +1 -0
  41. package/esm/index.d.mts +27 -3
  42. package/esm/index.mjs +27 -1
  43. package/esm/middleware/index.d.mts +4 -1
  44. package/esm/middleware/index.mjs +3 -0
  45. package/esm/middleware/one-time-token-throttle.middleware.d.mts +36 -0
  46. package/esm/middleware/one-time-token-throttle.middleware.d.mts.map +1 -0
  47. package/esm/middleware/one-time-token-throttle.middleware.mjs +53 -0
  48. package/esm/middleware/one-time-token-throttle.middleware.mjs.map +1 -0
  49. package/esm/middleware/otp-throttle.middleware.d.mts +30 -0
  50. package/esm/middleware/otp-throttle.middleware.d.mts.map +1 -0
  51. package/esm/middleware/otp-throttle.middleware.mjs +45 -0
  52. package/esm/middleware/otp-throttle.middleware.mjs.map +1 -0
  53. package/esm/middleware/require-verified-email.middleware.d.mts +18 -0
  54. package/esm/middleware/require-verified-email.middleware.d.mts.map +1 -0
  55. package/esm/middleware/require-verified-email.middleware.mjs +24 -0
  56. package/esm/middleware/require-verified-email.middleware.mjs.map +1 -0
  57. package/esm/models/access-token/access-token.model.d.mts +8 -0
  58. package/esm/models/access-token/access-token.model.d.mts.map +1 -1
  59. package/esm/models/access-token/access-token.model.mjs +3 -0
  60. package/esm/models/access-token/access-token.model.mjs.map +1 -1
  61. package/esm/models/index.d.mts +3 -1
  62. package/esm/models/index.d.mts.map +1 -1
  63. package/esm/models/index.mjs +16 -1
  64. package/esm/models/index.mjs.map +1 -1
  65. package/esm/models/one-time-token/index.d.mts +1 -0
  66. package/esm/models/one-time-token/index.mjs +3 -0
  67. package/esm/models/one-time-token/migration.mjs +31 -0
  68. package/esm/models/one-time-token/migration.mjs.map +1 -0
  69. package/esm/models/one-time-token/one-time-token.model.d.mts +173 -0
  70. package/esm/models/one-time-token/one-time-token.model.d.mts.map +1 -0
  71. package/esm/models/one-time-token/one-time-token.model.mjs +151 -0
  72. package/esm/models/one-time-token/one-time-token.model.mjs.map +1 -0
  73. package/esm/models/passkey-credential/index.d.mts +1 -0
  74. package/esm/models/passkey-credential/index.mjs +3 -0
  75. package/esm/models/passkey-credential/migration.mjs +25 -0
  76. package/esm/models/passkey-credential/migration.mjs.map +1 -0
  77. package/esm/models/passkey-credential/passkey-credential.model.d.mts +108 -0
  78. package/esm/models/passkey-credential/passkey-credential.model.d.mts.map +1 -0
  79. package/esm/models/passkey-credential/passkey-credential.model.mjs +70 -0
  80. package/esm/models/passkey-credential/passkey-credential.model.mjs.map +1 -0
  81. package/esm/models/provider-account/index.d.mts +1 -0
  82. package/esm/models/provider-account/index.mjs +3 -0
  83. package/esm/models/provider-account/migration.mjs +25 -0
  84. package/esm/models/provider-account/migration.mjs.map +1 -0
  85. package/esm/models/provider-account/provider-account.model.d.mts +75 -0
  86. package/esm/models/provider-account/provider-account.model.d.mts.map +1 -0
  87. package/esm/models/provider-account/provider-account.model.mjs +55 -0
  88. package/esm/models/provider-account/provider-account.model.mjs.map +1 -0
  89. package/esm/models/refresh-token/refresh-token.model.d.mts +7 -0
  90. package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
  91. package/esm/models/refresh-token/refresh-token.model.mjs +3 -0
  92. package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
  93. package/esm/otp/index.d.mts +1 -0
  94. package/esm/otp/index.mjs +3 -0
  95. package/esm/otp/otp.d.mts +38 -0
  96. package/esm/otp/otp.d.mts.map +1 -0
  97. package/esm/otp/otp.mjs +105 -0
  98. package/esm/otp/otp.mjs.map +1 -0
  99. package/esm/passkeys/index.d.mts +3 -0
  100. package/esm/passkeys/index.mjs +4 -0
  101. package/esm/passkeys/passkey-authentication.d.mts +33 -0
  102. package/esm/passkeys/passkey-authentication.d.mts.map +1 -0
  103. package/esm/passkeys/passkey-authentication.mjs +93 -0
  104. package/esm/passkeys/passkey-authentication.mjs.map +1 -0
  105. package/esm/passkeys/passkey-challenges.mjs +53 -0
  106. package/esm/passkeys/passkey-challenges.mjs.map +1 -0
  107. package/esm/passkeys/passkey-registration.d.mts +29 -0
  108. package/esm/passkeys/passkey-registration.d.mts.map +1 -0
  109. package/esm/passkeys/passkey-registration.mjs +88 -0
  110. package/esm/passkeys/passkey-registration.mjs.map +1 -0
  111. package/esm/passkeys/simplewebauthn.d.mts +21 -0
  112. package/esm/passkeys/simplewebauthn.d.mts.map +1 -0
  113. package/esm/passkeys/simplewebauthn.mjs +13 -0
  114. package/esm/passkeys/simplewebauthn.mjs.map +1 -0
  115. package/esm/providers/google-provider.d.mts +40 -0
  116. package/esm/providers/google-provider.d.mts.map +1 -0
  117. package/esm/providers/google-provider.mjs +105 -0
  118. package/esm/providers/google-provider.mjs.map +1 -0
  119. package/esm/providers/index.d.mts +2 -0
  120. package/esm/providers/index.mjs +4 -0
  121. package/esm/providers/provider-login.d.mts +43 -0
  122. package/esm/providers/provider-login.d.mts.map +1 -0
  123. package/esm/providers/provider-login.mjs +86 -0
  124. package/esm/providers/provider-login.mjs.map +1 -0
  125. package/esm/providers/provider-state-cookie.mjs +63 -0
  126. package/esm/providers/provider-state-cookie.mjs.map +1 -0
  127. package/esm/providers/resolve-provider-user.mjs +45 -0
  128. package/esm/providers/resolve-provider-user.mjs.map +1 -0
  129. package/esm/services/auth-config.mjs +72 -0
  130. package/esm/services/auth-config.mjs.map +1 -1
  131. package/esm/services/auth.service.d.mts +18 -1
  132. package/esm/services/auth.service.d.mts.map +1 -1
  133. package/esm/services/auth.service.mjs +28 -1
  134. package/esm/services/auth.service.mjs.map +1 -1
  135. package/esm/services/email-verification.d.mts +35 -0
  136. package/esm/services/email-verification.d.mts.map +1 -0
  137. package/esm/services/email-verification.mjs +55 -0
  138. package/esm/services/email-verification.mjs.map +1 -0
  139. package/esm/services/index.d.mts +4 -1
  140. package/esm/services/index.mjs +3 -0
  141. package/esm/services/one-time-tokens.d.mts +32 -0
  142. package/esm/services/one-time-tokens.d.mts.map +1 -0
  143. package/esm/services/one-time-tokens.mjs +56 -0
  144. package/esm/services/one-time-tokens.mjs.map +1 -0
  145. package/esm/services/optional-peer.mjs +37 -0
  146. package/esm/services/optional-peer.mjs.map +1 -0
  147. package/esm/services/password-reset.d.mts +34 -0
  148. package/esm/services/password-reset.d.mts.map +1 -0
  149. package/esm/services/password-reset.mjs +81 -0
  150. package/esm/services/password-reset.mjs.map +1 -0
  151. package/esm/services/token-notifications.mjs +87 -0
  152. package/esm/services/token-notifications.mjs.map +1 -0
  153. package/esm/utils/auth-error-codes.d.mts +39 -1
  154. package/esm/utils/auth-error-codes.d.mts.map +1 -1
  155. package/esm/utils/auth-error-codes.mjs +33 -0
  156. package/esm/utils/auth-error-codes.mjs.map +1 -1
  157. package/esm/utils/index.mjs +1 -0
  158. package/esm/utils/safe-equal.d.mts +6 -0
  159. package/esm/utils/safe-equal.d.mts.map +1 -0
  160. package/esm/utils/safe-equal.mjs +13 -0
  161. package/esm/utils/safe-equal.mjs.map +1 -0
  162. package/llms-full.txt +313 -10
  163. package/llms.txt +2 -0
  164. package/package.json +21 -7
  165. package/skills/login-with-providers/SKILL.md +154 -0
  166. package/skills/manage-tokens/SKILL.md +7 -1
  167. package/skills/register-user/SKILL.md +6 -8
  168. package/skills/run-auth-commands/SKILL.md +1 -1
  169. package/skills/verify-email-and-reset-password/SKILL.md +137 -0
@@ -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 { };
@@ -0,0 +1,25 @@
1
+ import { ProviderAccount } from "./provider-account.model.mjs";
2
+ import { migrate } from "@warlock.js/cascade";
3
+
4
+ //#region ../auth/src/models/provider-account/migration.ts
5
+ const ProviderAccountMigration = migrate(ProviderAccount, {
6
+ name: "providerAccount",
7
+ up() {
8
+ this.createTableIfNotExists();
9
+ this.primaryUuid();
10
+ this.string("provider", 50);
11
+ this.string("provider_user_id", 255);
12
+ this.foreignId("user_id").index();
13
+ this.string("user_type", 50).nullable();
14
+ this.string("email", 255).nullable();
15
+ this.unique(["provider", "provider_user_id"]);
16
+ this.timestamps();
17
+ },
18
+ down() {
19
+ this.dropTableIfExists();
20
+ }
21
+ });
22
+
23
+ //#endregion
24
+ export { ProviderAccountMigration };
25
+ //# sourceMappingURL=migration.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.mjs","names":[],"sources":["../../../../../../../../auth/src/models/provider-account/migration.ts"],"sourcesContent":["import { migrate } from \"@warlock.js/cascade\";\nimport { ProviderAccount } from \"./provider-account.model\";\n\nexport const ProviderAccountMigration = migrate(ProviderAccount, {\n name: \"providerAccount\",\n up() {\n this.createTableIfNotExists();\n\n this.primaryUuid();\n\n this.string(\"provider\", 50);\n this.string(\"provider_user_id\", 255);\n this.foreignId(\"user_id\").index();\n this.string(\"user_type\", 50).nullable();\n this.string(\"email\", 255).nullable();\n\n // One app user per provider identity.\n this.unique([\"provider\", \"provider_user_id\"]);\n\n this.timestamps();\n },\n down() {\n this.dropTableIfExists();\n },\n});\n"],"mappings":";;;;AAGA,MAAa,2BAA2B,QAAQ,iBAAiB;CAC/D,MAAM;CACN,KAAK;EACH,KAAK,uBAAuB;EAE5B,KAAK,YAAY;EAEjB,KAAK,OAAO,YAAY,EAAE;EAC1B,KAAK,OAAO,oBAAoB,GAAG;EACnC,KAAK,UAAU,SAAS,CAAC,CAAC,MAAM;EAChC,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,SAAS;EACtC,KAAK,OAAO,SAAS,GAAG,CAAC,CAAC,SAAS;EAGnC,KAAK,OAAO,CAAC,YAAY,kBAAkB,CAAC;EAE5C,KAAK,WAAW;CAClB;CACA,OAAO;EACL,KAAK,kBAAkB;CACzB;AACF,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { Auth } from "../auth.model.mjs";
2
+ import { Model } from "@warlock.js/cascade";
3
+
4
+ //#region ../auth/src/models/provider-account/provider-account.model.d.ts
5
+ /** Seal schema for a provider link. Exported so an override can extend it. */
6
+ declare const providerAccountSchema: import("@warlock.js/seal").ObjectValidator<{
7
+ provider: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
8
+ provider_user_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
9
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
10
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
11
+ email: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
12
+ isOptional: true;
13
+ };
14
+ }> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
15
+ provider: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
16
+ provider_user_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
17
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
18
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
19
+ email: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
20
+ isOptional: true;
21
+ };
22
+ }>, import("@warlock.js/seal").InferInputObjectShape<{
23
+ provider: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
24
+ provider_user_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
25
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
26
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
27
+ email: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
28
+ isOptional: true;
29
+ };
30
+ }>>;
31
+ /**
32
+ * Links a provider identity (`provider` + the provider's stable user id) to
33
+ * an app user. Created the first time a VERIFIED provider email is matched or
34
+ * a user is created for it; afterwards the link alone resolves the user, so a
35
+ * later email change at the provider cannot move the login to another account.
36
+ */
37
+ declare class ProviderAccount extends Model {
38
+ static table: string;
39
+ static schema: import("@warlock.js/seal").ObjectValidator<{
40
+ provider: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
41
+ provider_user_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
42
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
43
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
44
+ email: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
45
+ isOptional: true;
46
+ };
47
+ }> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
48
+ provider: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
49
+ provider_user_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
50
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
51
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
52
+ email: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
53
+ isOptional: true;
54
+ };
55
+ }>, import("@warlock.js/seal").InferInputObjectShape<{
56
+ provider: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
57
+ provider_user_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
58
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
59
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
60
+ email: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
61
+ isOptional: true;
62
+ };
63
+ }>>;
64
+ /** The linked user's id. */
65
+ get userId(): any;
66
+ /** The linked user's type slug. */
67
+ get userType(): string;
68
+ /** Find the link for a provider identity. */
69
+ static findLink(provider: string, providerUserId: string): Promise<ProviderAccount | null>;
70
+ /** Link a provider identity to a user. */
71
+ static link(provider: string, providerUserId: string, user: Auth, email?: string): Promise<ProviderAccount>;
72
+ }
73
+ //#endregion
74
+ export { ProviderAccount, providerAccountSchema };
75
+ //# sourceMappingURL=provider-account.model.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-account.model.d.mts","names":[],"sources":["../../../../../../../../auth/src/models/provider-account/provider-account.model.ts"],"mappings":";;;;;cAKa,qBAAA,6BAAqB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAcrB,eAAA,SAAwB,KAAA;EAAA,OACrB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;MAGT,MAAA;EANgB;EAAA,IAWhB,QAAA;EAQA;EAAA,OAHG,QAAA,CACZ,QAAA,UACA,cAAA,WACC,OAAA,CAAQ,eAAA;EAKwD;EAAA,OAArD,IAAA,CAAK,QAAA,UAAkB,cAAA,UAAwB,IAAA,EAAM,IAAA,EAAM,KAAA,YAAc,OAAA,CAAA,eAAA;AAAA"}
@@ -0,0 +1,55 @@
1
+ import { Model } from "@warlock.js/cascade";
2
+ import { v } from "@warlock.js/seal";
3
+
4
+ //#region ../auth/src/models/provider-account/provider-account.model.ts
5
+ /** Seal schema for a provider link. Exported so an override can extend it. */
6
+ const providerAccountSchema = v.object({
7
+ provider: v.string().required(),
8
+ provider_user_id: v.string().required(),
9
+ user_id: v.scalar().required(),
10
+ user_type: v.string().required(),
11
+ email: v.string().optional()
12
+ });
13
+ /**
14
+ * Links a provider identity (`provider` + the provider's stable user id) to
15
+ * an app user. Created the first time a VERIFIED provider email is matched or
16
+ * a user is created for it; afterwards the link alone resolves the user, so a
17
+ * later email change at the provider cannot move the login to another account.
18
+ */
19
+ var ProviderAccount = class extends Model {
20
+ static {
21
+ this.table = "provider_accounts";
22
+ }
23
+ static {
24
+ this.schema = providerAccountSchema;
25
+ }
26
+ /** The linked user's id. */
27
+ get userId() {
28
+ return this.get("user_id");
29
+ }
30
+ /** The linked user's type slug. */
31
+ get userType() {
32
+ return this.get("user_type");
33
+ }
34
+ /** Find the link for a provider identity. */
35
+ static findLink(provider, providerUserId) {
36
+ return this.first({
37
+ provider,
38
+ provider_user_id: providerUserId
39
+ });
40
+ }
41
+ /** Link a provider identity to a user. */
42
+ static link(provider, providerUserId, user, email) {
43
+ return this.create({
44
+ provider,
45
+ provider_user_id: providerUserId,
46
+ user_id: user.id,
47
+ user_type: user.userType,
48
+ ...email ? { email } : {}
49
+ });
50
+ }
51
+ };
52
+
53
+ //#endregion
54
+ export { ProviderAccount, providerAccountSchema };
55
+ //# sourceMappingURL=provider-account.model.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-account.model.mjs","names":[],"sources":["../../../../../../../../auth/src/models/provider-account/provider-account.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 provider link. Exported so an override can extend it. */\nexport const providerAccountSchema = v.object({\n provider: v.string().required(),\n provider_user_id: v.string().required(),\n user_id: v.scalar().required(),\n user_type: v.string().required(),\n email: v.string().optional(),\n});\n\n/**\n * Links a provider identity (`provider` + the provider's stable user id) to\n * an app user. Created the first time a VERIFIED provider email is matched or\n * a user is created for it; afterwards the link alone resolves the user, so a\n * later email change at the provider cannot move the login to another account.\n */\nexport class ProviderAccount extends Model {\n public static table = \"provider_accounts\";\n\n public static schema = providerAccountSchema;\n\n /** The linked user's id. */\n public get userId() {\n return this.get(\"user_id\");\n }\n\n /** The linked user's type slug. */\n public get userType(): string {\n return this.get(\"user_type\");\n }\n\n /** Find the link for a provider identity. */\n public static findLink(\n provider: string,\n providerUserId: string,\n ): Promise<ProviderAccount | null> {\n return this.first({ provider, provider_user_id: providerUserId });\n }\n\n /** Link a provider identity to a user. */\n public static link(provider: string, providerUserId: string, user: Auth, email?: string) {\n return this.create({\n provider,\n provider_user_id: providerUserId,\n user_id: user.id,\n user_type: user.userType,\n ...(email ? { email } : {}),\n });\n }\n}\n"],"mappings":";;;;;AAKA,MAAa,wBAAwB,EAAE,OAAO;CAC5C,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,kBAAkB,EAAE,OAAO,CAAC,CAAC,SAAS;CACtC,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;AAC7B,CAAC;;;;;;;AAQD,IAAa,kBAAb,cAAqC,MAAM;;eACnB;;;gBAEC;;;CAGvB,IAAW,SAAS;EAClB,OAAO,KAAK,IAAI,SAAS;CAC3B;;CAGA,IAAW,WAAmB;EAC5B,OAAO,KAAK,IAAI,WAAW;CAC7B;;CAGA,OAAc,SACZ,UACA,gBACiC;EACjC,OAAO,KAAK,MAAM;GAAE;GAAU,kBAAkB;EAAe,CAAC;CAClE;;CAGA,OAAc,KAAK,UAAkB,gBAAwB,MAAY,OAAgB;EACvF,OAAO,KAAK,OAAO;GACjB;GACA,kBAAkB;GAClB,SAAS,KAAK;GACd,WAAW,KAAK;GAChB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EAC3B,CAAC;CACH;AACF"}
@@ -139,6 +139,13 @@ declare class RefreshToken extends Model {
139
139
  isOptional: true;
140
140
  };
141
141
  }>>;
142
+ /**
143
+ * Permanent regardless of the data source default — Mongo's driver default
144
+ * is `"trash"`, which would copy live credential material (the JWT itself)
145
+ * into `refresh_tokensTrash` on every `destroy()` (expiry purge,
146
+ * never-expiring purge). A purged refresh token must not survive anywhere.
147
+ */
148
+ static deleteStrategy: "permanent";
142
149
  /** Token family this row belongs to (rotation / replay grouping). */
143
150
  get familyId(): string;
144
151
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-token.model.d.mts","names":[],"sources":["../../../../../../../../auth/src/models/refresh-token/refresh-token.model.ts"],"mappings":";;;;;;;AAkBA;;;;;;;;;;cAAa,kBAAA,6BAAkB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAcnB,wBAAA;EACV,QAAA;EACA,SAAA;EACA,UAAA,GAAa,UAAU;AAAA;;;;;;;;;;;cAaZ,YAAA,SAAqB,KAAA;EAAA,OAClB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAGT,QAAA;;;;;;;;;;;MAcA,SAAA;;;AApCb;;MAgDa,YAAA;EA7CY;EAAA,IAkDZ,SAAA;EAnDX;EAAA,IAwDW,OAAA;EAvDE;;AAAU;EA8DV,MAAA,IAAU,OAAA;EAjDC;;;;;;;EA4DX,cAAA,IAAkB,OAAA;;;;EAclB,UAAA,IAAc,OAAA;;;;SAOb,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,OAAA,EAAS,wBAAA,GAAwB,OAAA,CAAA,YAAA;;;;SAoBlE,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,YAAA;;;;;SAQpC,WAAA,CAAY,IAAA,EAAM,IAAA,EAAM,KAAA,WAAgB,OAAA,CAAQ,YAAA;;;;SAOhD,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA,WAAa,OAAA;EAnC3B;;;EAAA,OA0CZ,SAAA,CAAU,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;EAtBK;;;;;;;;EAAA,OAsC9B,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;EAhBjB;;;;EAAA,OAgCjB,YAAA,CAAa,QAAA,WAAmB,OAAA,CAAQ,YAAA;EAAR;;;;;EAAA,OAehC,UAAA,CAAW,IAAA,EAAM,IAAA,EAAM,GAAA,WAAc,OAAA;EAoChB;;;;;EAAA,OAhBrB,YAAA,IAAgB,OAAA,CAAQ,YAAA;EA9LZ;;;;;;EAAA,OA8MZ,iBAAA,IAAqB,OAAA,CAAQ,YAAA;;;;;;;;;;SAe7B,kBAAA,IAAsB,OAAA,CAAQ,YAAA;AAAA"}
1
+ {"version":3,"file":"refresh-token.model.d.mts","names":[],"sources":["../../../../../../../../auth/src/models/refresh-token/refresh-token.model.ts"],"mappings":";;;;;;;AAkBA;;;;;;;;;;cAAa,kBAAA,6BAAkB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAcnB,wBAAA;EACV,QAAA;EACA,SAAA;EACA,UAAA,GAAa,UAAU;AAAA;;;;;;;;;;;cAaZ,YAAA,SAAqB,KAAA;EAAA,OAClB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAQN,cAAA;;MAGH,QAAA;;;;;;;AA9Bb;;;;MA4Ca,SAAA;EA1CX;;;;EAAA,IAsDW,YAAA;EAxCA;EAAA,IA6CA,SAAA;;MAKA,OAAA;;;;EAOE,MAAA,IAAU,OAAA;;;;;;;;EAWV,cAAA,IAAkB,OAAA;;;;EAclB,UAAA,IAAc,OAAA;;;;SAOb,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,OAAA,EAAS,wBAAA,GAAwB,OAAA,CAAA,YAAA;;;;SAoBlE,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,YAAA;EAzCnB;;;;EAAA,OAiDjB,WAAA,CAAY,IAAA,EAAM,IAAA,EAAM,KAAA,WAAgB,OAAA,CAAQ,YAAA;EA5BkB;;;EAAA,OAmClE,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA,WAAa,OAAA;EAPS;;;EAAA,OAchD,SAAA,CAAU,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;EAAf;;;;;;;;EAAA,OAgBV,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;EA+BG;;;;EAAA,OAfrC,YAAA,CAAa,QAAA,WAAmB,OAAA,CAAQ,YAAA;EAkEV;;;;;EAAA,OAnD9B,UAAA,CAAW,IAAA,EAAM,IAAA,EAAM,GAAA,WAAc,OAAA;EAjL3C;;;;;EAAA,OAqMM,YAAA,IAAgB,OAAA,CAAQ,YAAA;;;;;;;SAgBxB,iBAAA,IAAqB,OAAA,CAAQ,YAAA;;;;;;;;;;SAe7B,kBAAA,IAAsB,OAAA,CAAQ,YAAA;AAAA"}
@@ -42,6 +42,9 @@ var RefreshToken = class extends Model {
42
42
  static {
43
43
  this.schema = refreshTokenSchema;
44
44
  }
45
+ static {
46
+ this.deleteStrategy = "permanent";
47
+ }
45
48
  /** Token family this row belongs to (rotation / replay grouping). */
46
49
  get familyId() {
47
50
  return this.get("family_id");
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-token.model.mjs","names":[],"sources":["../../../../../../../../auth/src/models/refresh-token/refresh-token.model.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\r\nimport { v } from \"@warlock.js/seal\";\r\nimport type { DeviceInfo } from \"../../contracts/types\";\r\nimport { isNeverExpiring, isUsableExpiry } from \"../../utils/token-expiry\";\r\nimport type { Auth } from \"../auth.model\";\r\n\r\n/**\r\n * Seal schema for the persisted refresh-token record. Exported so an override\r\n * can spread it and add columns (e.g. a tenant key) without re-declaring the\r\n * base shape:\r\n *\r\n * @example\r\n * export class AppRefreshToken extends RefreshToken {\r\n * public static schema = refreshTokenSchema.extend({\r\n * organization_id: v.string().required(),\r\n * });\r\n * }\r\n */\r\nexport const refreshTokenSchema = v.object({\r\n token: v.string().required(),\r\n user_id: v.scalar().required(),\r\n user_type: v.string().required(),\r\n family_id: v.string().required(),\r\n expires_at: v.date().required(),\r\n last_used_at: v.date().default(() => new Date()),\r\n revoked_at: v.date().optional(),\r\n device_info: v.record(v.any()).optional(),\r\n});\r\n\r\n/**\r\n * Extra attributes captured when a refresh token is issued.\r\n */\r\nexport type RefreshTokenIssueOptions = {\r\n familyId: string;\r\n expiresAt: string;\r\n deviceInfo?: DeviceInfo;\r\n};\r\n\r\n/**\r\n * Persisted refresh-token record + the data layer for refresh tokens.\r\n *\r\n * **Role.** Owns every refresh-token read, write, and lifecycle transition.\r\n * The auth service drives refresh-token state exclusively through this model's\r\n * named statics and instance methods, so it never hard-codes a column name —\r\n * which is what makes a snake/camel mismatch (the historical `userId` bug)\r\n * structurally impossible and lets an override rename or add columns by\r\n * extending this class and registering it under `config.auth.refreshToken.model`.\r\n */\r\nexport class RefreshToken extends Model {\r\n public static table = \"refresh_tokens\";\r\n\r\n public static schema = refreshTokenSchema;\r\n\r\n /** Token family this row belongs to (rotation / replay grouping). */\r\n public get familyId(): string {\r\n return this.get(\"family_id\");\r\n }\r\n\r\n /**\r\n * Whether the token's `expires_at` is in the past.\r\n *\r\n * **Fails closed as of 4.12.0**: a missing or unparseable `expires_at` now\r\n * counts as expired. It previously answered `false` for both — \"no expiry\r\n * recorded ⇒ never expires\" — which handed an unlimited life to precisely the\r\n * malformed rows, including the `Invalid Date` a pre-4.12.0 unparseable\r\n * `expiresIn` could write. `expires_at` is `required` in the schema, so a row\r\n * that cannot say when it dies is malformed, not immortal.\r\n */\r\n public get isExpired(): boolean {\r\n const expiresAt = this.get(\"expires_at\");\r\n\r\n if (!isUsableExpiry(expiresAt)) return true;\r\n\r\n return new Date().getTime() > new Date(expiresAt).getTime();\r\n }\r\n\r\n /**\r\n * Whether nothing can ever retire this row — an unusable `expires_at`, or a\r\n * token carrying no `exp` claim. See {@link isNeverExpiring}.\r\n */\r\n public get neverExpires(): boolean {\r\n return isNeverExpiring(this.get(\"token\"), this.get(\"expires_at\"));\r\n }\r\n\r\n /** Whether the token has been revoked. */\r\n public get isRevoked(): boolean {\r\n return !!this.get(\"revoked_at\");\r\n }\r\n\r\n /** Whether the token is still usable (not expired and not revoked). */\r\n public get isValid(): boolean {\r\n return !this.isExpired && !this.isRevoked;\r\n }\r\n\r\n /**\r\n * Unconditionally stamp `revoked_at` on this token.\r\n */\r\n public async revoke(): Promise<this> {\r\n return this.merge({ revoked_at: new Date() }).save();\r\n }\r\n\r\n /**\r\n * Atomically revoke this token ONLY if it is still active. Resolves to `true`\r\n * when this call performed the revoke, `false` when a concurrent request had\r\n * already revoked it — the win/lose signal that powers rotation replay\r\n * detection. Uses a conditional UPDATE so two concurrent rotations of the\r\n * same token can never both succeed.\r\n */\r\n public async revokeIfActive(): Promise<boolean> {\r\n const modelClass = this.constructor as typeof RefreshToken;\r\n\r\n const revokedCount = await modelClass.atomic(\r\n { id: this.id, revoked_at: null },\r\n { $set: { revoked_at: new Date() } },\r\n );\r\n\r\n return revokedCount > 0;\r\n }\r\n\r\n /**\r\n * Touch `last_used_at` without revoking — the non-rotating refresh path.\r\n */\r\n public async markAsUsed(): Promise<void> {\r\n await this.merge({ last_used_at: new Date() }).save();\r\n }\r\n\r\n /**\r\n * Persist a freshly-signed refresh token for the user.\r\n */\r\n public static issue(user: Auth, token: string, options: RefreshTokenIssueOptions) {\r\n return this.create({\r\n token,\r\n user_id: user.id,\r\n user_type: user.userType,\r\n family_id: options.familyId,\r\n expires_at: options.expiresAt,\r\n device_info: options.deviceInfo\r\n ? {\r\n userAgent: options.deviceInfo.userAgent,\r\n ip: options.deviceInfo.ip,\r\n deviceId: options.deviceInfo.deviceId,\r\n }\r\n : undefined,\r\n });\r\n }\r\n\r\n /**\r\n * Find a refresh-token row by its raw token string.\r\n */\r\n public static findByToken(token: string): Promise<RefreshToken | null> {\r\n return this.first({ token });\r\n }\r\n\r\n /**\r\n * Find a refresh token scoped to a user — used by logout so a caller can only\r\n * revoke a token that actually belongs to them.\r\n */\r\n public static findForUser(user: Auth, token: string): Promise<RefreshToken | null> {\r\n return this.first({ token, user_id: user.id });\r\n }\r\n\r\n /**\r\n * Delete a specific refresh token belonging to the user.\r\n */\r\n public static deleteForUser(user: Auth, token: string) {\r\n return this.delete({ token, user_id: user.id });\r\n }\r\n\r\n /**\r\n * Active, unexpired sessions for the user, newest first.\r\n */\r\n public static activeFor(user: Auth): Promise<RefreshToken[]> {\r\n return this.query()\r\n .where({ user_id: user.id, user_type: user.userType, revoked_at: null })\r\n .where(\"expires_at\", \">\", new Date())\r\n .orderBy(\"created_at\", \"desc\")\r\n .get();\r\n }\r\n\r\n /**\r\n * Revoke every still-active refresh token for the user, returning the rows\r\n * that were revoked so the caller can emit a per-token event for each.\r\n *\r\n * Rows are fetched BEFORE they are revoked: a bulk `findAndUpdate` keyed on\r\n * `revoked_at: null` would re-query the same predicate after the update and\r\n * match nothing, returning an empty set.\r\n */\r\n public static async revokeAllFor(user: Auth): Promise<RefreshToken[]> {\r\n const tokens = await this.query()\r\n .where({ user_id: user.id, user_type: user.userType, revoked_at: null })\r\n .get();\r\n\r\n for (const token of tokens) {\r\n await token.revoke();\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n /**\r\n * Revoke every still-active token in a family (rotation breach containment),\r\n * returning the revoked rows (fetched before revocation — see `revokeAllFor`).\r\n */\r\n public static async revokeFamily(familyId: string): Promise<RefreshToken[]> {\r\n const tokens = await this.query().where({ family_id: familyId, revoked_at: null }).get();\r\n\r\n for (const token of tokens) {\r\n await token.revoke();\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n /**\r\n * Revoke the oldest active tokens so at most `max - 1` remain — making room\r\n * for the about-to-be-issued one. Bounded by `max`, so the per-row loop is\r\n * small.\r\n */\r\n public static async enforceMax(user: Auth, max: number): Promise<void> {\r\n const activeTokens = await this.query()\r\n .where({ user_id: user.id, user_type: user.userType, revoked_at: null })\r\n .orderBy(\"created_at\", \"asc\")\r\n .get();\r\n\r\n if (activeTokens.length < max) return;\r\n\r\n const tokensToRevoke = activeTokens.slice(0, activeTokens.length - max + 1);\r\n\r\n for (const token of tokensToRevoke) {\r\n await token.revoke();\r\n }\r\n }\r\n\r\n /**\r\n * Hard-delete every expired refresh token, returning the deleted rows so the\r\n * caller can emit a per-token event. Runs from the `auth.cleanup` CLI command\r\n * (a cold batch path).\r\n */\r\n public static async purgeExpired(): Promise<RefreshToken[]> {\r\n const expiredTokens = await this.query().where(\"expires_at\", \"<\", new Date()).get();\r\n\r\n for (const token of expiredTokens) {\r\n await token.destroy();\r\n }\r\n\r\n return expiredTokens;\r\n }\r\n\r\n /**\r\n * Every row that can never retire itself — see {@link neverExpires}. A full\r\n * scan filtered in memory, for the reason given on\r\n * {@link AccessToken.findNeverExpiring}: neither an `Invalid Date` nor a\r\n * missing `exp` claim is expressible as a `where`.\r\n */\r\n public static async findNeverExpiring(): Promise<RefreshToken[]> {\r\n const tokens = await this.query().get();\r\n\r\n return tokens.filter((token: RefreshToken) => token.neverExpires);\r\n }\r\n\r\n /**\r\n * Hard-delete every never-expiring row, returning the rows removed.\r\n *\r\n * Deleted rather than `revoked_at`-stamped: a revoked row is still a row this\r\n * table has to carry, and one of the two shapes being removed is a row whose\r\n * date column cannot be compared at all — leaving it in place keeps a\r\n * permanently unpurgeable record. Rotation replay-detection is unaffected;\r\n * these rows can no longer be presented successfully either way.\r\n */\r\n public static async purgeNeverExpiring(): Promise<RefreshToken[]> {\r\n const tokens = await this.findNeverExpiring();\r\n\r\n for (const token of tokens) {\r\n await token.destroy();\r\n }\r\n\r\n return tokens;\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"refresh-token.model.mjs","names":[],"sources":["../../../../../../../../auth/src/models/refresh-token/refresh-token.model.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\r\nimport { v } from \"@warlock.js/seal\";\r\nimport type { DeviceInfo } from \"../../contracts/types\";\r\nimport { isNeverExpiring, isUsableExpiry } from \"../../utils/token-expiry\";\r\nimport type { Auth } from \"../auth.model\";\r\n\r\n/**\r\n * Seal schema for the persisted refresh-token record. Exported so an override\r\n * can spread it and add columns (e.g. a tenant key) without re-declaring the\r\n * base shape:\r\n *\r\n * @example\r\n * export class AppRefreshToken extends RefreshToken {\r\n * public static schema = refreshTokenSchema.extend({\r\n * organization_id: v.string().required(),\r\n * });\r\n * }\r\n */\r\nexport const refreshTokenSchema = v.object({\r\n token: v.string().required(),\r\n user_id: v.scalar().required(),\r\n user_type: v.string().required(),\r\n family_id: v.string().required(),\r\n expires_at: v.date().required(),\r\n last_used_at: v.date().default(() => new Date()),\r\n revoked_at: v.date().optional(),\r\n device_info: v.record(v.any()).optional(),\r\n});\r\n\r\n/**\r\n * Extra attributes captured when a refresh token is issued.\r\n */\r\nexport type RefreshTokenIssueOptions = {\r\n familyId: string;\r\n expiresAt: string;\r\n deviceInfo?: DeviceInfo;\r\n};\r\n\r\n/**\r\n * Persisted refresh-token record + the data layer for refresh tokens.\r\n *\r\n * **Role.** Owns every refresh-token read, write, and lifecycle transition.\r\n * The auth service drives refresh-token state exclusively through this model's\r\n * named statics and instance methods, so it never hard-codes a column name —\r\n * which is what makes a snake/camel mismatch (the historical `userId` bug)\r\n * structurally impossible and lets an override rename or add columns by\r\n * extending this class and registering it under `config.auth.refreshToken.model`.\r\n */\r\nexport class RefreshToken extends Model {\r\n public static table = \"refresh_tokens\";\r\n\r\n public static schema = refreshTokenSchema;\r\n\r\n /**\r\n * Permanent regardless of the data source default — Mongo's driver default\r\n * is `\"trash\"`, which would copy live credential material (the JWT itself)\r\n * into `refresh_tokensTrash` on every `destroy()` (expiry purge,\r\n * never-expiring purge). A purged refresh token must not survive anywhere.\r\n */\r\n public static deleteStrategy = \"permanent\" as const;\r\n\r\n /** Token family this row belongs to (rotation / replay grouping). */\r\n public get familyId(): string {\r\n return this.get(\"family_id\");\r\n }\r\n\r\n /**\r\n * Whether the token's `expires_at` is in the past.\r\n *\r\n * **Fails closed as of 4.12.0**: a missing or unparseable `expires_at` now\r\n * counts as expired. It previously answered `false` for both — \"no expiry\r\n * recorded ⇒ never expires\" — which handed an unlimited life to precisely the\r\n * malformed rows, including the `Invalid Date` a pre-4.12.0 unparseable\r\n * `expiresIn` could write. `expires_at` is `required` in the schema, so a row\r\n * that cannot say when it dies is malformed, not immortal.\r\n */\r\n public get isExpired(): boolean {\r\n const expiresAt = this.get(\"expires_at\");\r\n\r\n if (!isUsableExpiry(expiresAt)) return true;\r\n\r\n return new Date().getTime() > new Date(expiresAt).getTime();\r\n }\r\n\r\n /**\r\n * Whether nothing can ever retire this row — an unusable `expires_at`, or a\r\n * token carrying no `exp` claim. See {@link isNeverExpiring}.\r\n */\r\n public get neverExpires(): boolean {\r\n return isNeverExpiring(this.get(\"token\"), this.get(\"expires_at\"));\r\n }\r\n\r\n /** Whether the token has been revoked. */\r\n public get isRevoked(): boolean {\r\n return !!this.get(\"revoked_at\");\r\n }\r\n\r\n /** Whether the token is still usable (not expired and not revoked). */\r\n public get isValid(): boolean {\r\n return !this.isExpired && !this.isRevoked;\r\n }\r\n\r\n /**\r\n * Unconditionally stamp `revoked_at` on this token.\r\n */\r\n public async revoke(): Promise<this> {\r\n return this.merge({ revoked_at: new Date() }).save();\r\n }\r\n\r\n /**\r\n * Atomically revoke this token ONLY if it is still active. Resolves to `true`\r\n * when this call performed the revoke, `false` when a concurrent request had\r\n * already revoked it — the win/lose signal that powers rotation replay\r\n * detection. Uses a conditional UPDATE so two concurrent rotations of the\r\n * same token can never both succeed.\r\n */\r\n public async revokeIfActive(): Promise<boolean> {\r\n const modelClass = this.constructor as typeof RefreshToken;\r\n\r\n const revokedCount = await modelClass.atomic(\r\n { id: this.id, revoked_at: null },\r\n { $set: { revoked_at: new Date() } },\r\n );\r\n\r\n return revokedCount > 0;\r\n }\r\n\r\n /**\r\n * Touch `last_used_at` without revoking — the non-rotating refresh path.\r\n */\r\n public async markAsUsed(): Promise<void> {\r\n await this.merge({ last_used_at: new Date() }).save();\r\n }\r\n\r\n /**\r\n * Persist a freshly-signed refresh token for the user.\r\n */\r\n public static issue(user: Auth, token: string, options: RefreshTokenIssueOptions) {\r\n return this.create({\r\n token,\r\n user_id: user.id,\r\n user_type: user.userType,\r\n family_id: options.familyId,\r\n expires_at: options.expiresAt,\r\n device_info: options.deviceInfo\r\n ? {\r\n userAgent: options.deviceInfo.userAgent,\r\n ip: options.deviceInfo.ip,\r\n deviceId: options.deviceInfo.deviceId,\r\n }\r\n : undefined,\r\n });\r\n }\r\n\r\n /**\r\n * Find a refresh-token row by its raw token string.\r\n */\r\n public static findByToken(token: string): Promise<RefreshToken | null> {\r\n return this.first({ token });\r\n }\r\n\r\n /**\r\n * Find a refresh token scoped to a user — used by logout so a caller can only\r\n * revoke a token that actually belongs to them.\r\n */\r\n public static findForUser(user: Auth, token: string): Promise<RefreshToken | null> {\r\n return this.first({ token, user_id: user.id });\r\n }\r\n\r\n /**\r\n * Delete a specific refresh token belonging to the user.\r\n */\r\n public static deleteForUser(user: Auth, token: string) {\r\n return this.delete({ token, user_id: user.id });\r\n }\r\n\r\n /**\r\n * Active, unexpired sessions for the user, newest first.\r\n */\r\n public static activeFor(user: Auth): Promise<RefreshToken[]> {\r\n return this.query()\r\n .where({ user_id: user.id, user_type: user.userType, revoked_at: null })\r\n .where(\"expires_at\", \">\", new Date())\r\n .orderBy(\"created_at\", \"desc\")\r\n .get();\r\n }\r\n\r\n /**\r\n * Revoke every still-active refresh token for the user, returning the rows\r\n * that were revoked so the caller can emit a per-token event for each.\r\n *\r\n * Rows are fetched BEFORE they are revoked: a bulk `findAndUpdate` keyed on\r\n * `revoked_at: null` would re-query the same predicate after the update and\r\n * match nothing, returning an empty set.\r\n */\r\n public static async revokeAllFor(user: Auth): Promise<RefreshToken[]> {\r\n const tokens = await this.query()\r\n .where({ user_id: user.id, user_type: user.userType, revoked_at: null })\r\n .get();\r\n\r\n for (const token of tokens) {\r\n await token.revoke();\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n /**\r\n * Revoke every still-active token in a family (rotation breach containment),\r\n * returning the revoked rows (fetched before revocation — see `revokeAllFor`).\r\n */\r\n public static async revokeFamily(familyId: string): Promise<RefreshToken[]> {\r\n const tokens = await this.query().where({ family_id: familyId, revoked_at: null }).get();\r\n\r\n for (const token of tokens) {\r\n await token.revoke();\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n /**\r\n * Revoke the oldest active tokens so at most `max - 1` remain — making room\r\n * for the about-to-be-issued one. Bounded by `max`, so the per-row loop is\r\n * small.\r\n */\r\n public static async enforceMax(user: Auth, max: number): Promise<void> {\r\n const activeTokens = await this.query()\r\n .where({ user_id: user.id, user_type: user.userType, revoked_at: null })\r\n .orderBy(\"created_at\", \"asc\")\r\n .get();\r\n\r\n if (activeTokens.length < max) return;\r\n\r\n const tokensToRevoke = activeTokens.slice(0, activeTokens.length - max + 1);\r\n\r\n for (const token of tokensToRevoke) {\r\n await token.revoke();\r\n }\r\n }\r\n\r\n /**\r\n * Hard-delete every expired refresh token, returning the deleted rows so the\r\n * caller can emit a per-token event. Runs from the `auth.cleanup` CLI command\r\n * (a cold batch path).\r\n */\r\n public static async purgeExpired(): Promise<RefreshToken[]> {\r\n const expiredTokens = await this.query().where(\"expires_at\", \"<\", new Date()).get();\r\n\r\n for (const token of expiredTokens) {\r\n await token.destroy();\r\n }\r\n\r\n return expiredTokens;\r\n }\r\n\r\n /**\r\n * Every row that can never retire itself — see {@link neverExpires}. A full\r\n * scan filtered in memory, for the reason given on\r\n * {@link AccessToken.findNeverExpiring}: neither an `Invalid Date` nor a\r\n * missing `exp` claim is expressible as a `where`.\r\n */\r\n public static async findNeverExpiring(): Promise<RefreshToken[]> {\r\n const tokens = await this.query().get();\r\n\r\n return tokens.filter((token: RefreshToken) => token.neverExpires);\r\n }\r\n\r\n /**\r\n * Hard-delete every never-expiring row, returning the rows removed.\r\n *\r\n * Deleted rather than `revoked_at`-stamped: a revoked row is still a row this\r\n * table has to carry, and one of the two shapes being removed is a row whose\r\n * date column cannot be compared at all — leaving it in place keeps a\r\n * permanently unpurgeable record. Rotation replay-detection is unaffected;\r\n * these rows can no longer be presented successfully either way.\r\n */\r\n public static async purgeNeverExpiring(): Promise<RefreshToken[]> {\r\n const tokens = await this.findNeverExpiring();\r\n\r\n for (const token of tokens) {\r\n await token.destroy();\r\n }\r\n\r\n return tokens;\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import { requestOtp, verifyOtp } from "./otp.mjs";
@@ -0,0 +1,3 @@
1
+ import { requestOtp, verifyOtp } from "./otp.mjs";
2
+
3
+ export { };
@@ -0,0 +1,38 @@
1
+ import { Auth } from "../models/auth.model.mjs";
2
+ import { DeviceInfo, LoginResult } from "../contracts/types.mjs";
3
+ import { ChildModel } from "@warlock.js/cascade";
4
+
5
+ //#region ../auth/src/otp/otp.d.ts
6
+ /**
7
+ * Send a 6-digit login code to the account whose `auth.otp.phoneField`
8
+ * (default `phone`) equals `phone`, through the notifications channel named by
9
+ * `options.channel` / `auth.otp.channel` (default `"sms"`). Auth ships no
10
+ * SMS/WhatsApp driver — the app registers that channel.
11
+ *
12
+ * The code is stored only as a keyed hash in `one_time_tokens` (purpose
13
+ * `otp`), expires after `auth.otp.expiresIn` (default 5m), and replaces any
14
+ * earlier unused code. **Anti-enumeration:** an unknown phone resolves the same
15
+ * way and sends nothing. Throttle the route with `otpRequestThrottleMiddleware()`.
16
+ *
17
+ * @throws AuthProviderSdkMissingError when `@warlock.js/notifications` is not
18
+ * installed and no `auth.otp.send` is configured.
19
+ */
20
+ declare function requestOtp<T extends Auth>(Model: ChildModel<T>, phone: string, options?: {
21
+ channel?: string;
22
+ }): Promise<void>;
23
+ /**
24
+ * Verify a phone login code and log the user in through
25
+ * `authService.completeLogin` — the same outcome as password login.
26
+ *
27
+ * Each check first counts an attempt atomically; once `auth.otp.maxAttempts`
28
+ * (default 5) have been counted the code is invalidated, so it is compared at
29
+ * most that many times even under concurrent guesses. A correct code is
30
+ * consumed single-use. Throttle the route with `otpVerifyThrottleMiddleware()`.
31
+ *
32
+ * @throws InvalidOneTimeTokenError (400, EC008) — the same for an unknown
33
+ * phone, a wrong, expired, used or exhausted code.
34
+ */
35
+ declare function verifyOtp<T extends Auth>(Model: ChildModel<T>, phone: string, code: string, deviceInfo?: DeviceInfo): Promise<LoginResult<T>>;
36
+ //#endregion
37
+ export { requestOtp, verifyOtp };
38
+ //# sourceMappingURL=otp.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otp.d.mts","names":[],"sources":["../../../../../../../auth/src/otp/otp.ts"],"mappings":";;;;;;;AAuFA;;;;;;;;;;;;iBAAsB,UAAA,WAAqB,IAAA,EACzC,KAAA,EAAO,UAAA,CAAW,CAAA,GAClB,KAAA,UACA,OAAA;EAAW,OAAA;AAAA,IACV,OAAA;;;;;AAAO;AAkCV;;;;;;;iBAAsB,SAAA,WAAoB,IAAA,EACxC,KAAA,EAAO,UAAA,CAAW,CAAA,GAClB,KAAA,UACA,IAAA,UACA,UAAA,GAAa,UAAA,GACZ,OAAA,CAAQ,WAAA,CAAY,CAAA"}