@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.
- package/CHANGELOG.md +65 -0
- package/README.md +88 -86
- package/esm/commands/auth-cleanup-command.mjs +1 -1
- package/esm/commands/auth-cleanup-command.mjs.map +1 -1
- package/esm/contracts/index.d.mts +4 -2
- package/esm/contracts/providers.d.mts +101 -0
- package/esm/contracts/providers.d.mts.map +1 -0
- package/esm/contracts/request-locals.d.mts +43 -0
- package/esm/contracts/request-locals.d.mts.map +1 -0
- package/esm/contracts/types.d.mts +159 -1
- package/esm/contracts/types.d.mts.map +1 -1
- package/esm/contracts/types.mjs.map +1 -1
- package/esm/errors/auth-provider-sdk-missing.error.d.mts +17 -0
- package/esm/errors/auth-provider-sdk-missing.error.d.mts.map +1 -0
- package/esm/errors/auth-provider-sdk-missing.error.mjs +22 -0
- package/esm/errors/auth-provider-sdk-missing.error.mjs.map +1 -0
- package/esm/errors/email-not-verified.error.d.mts +16 -0
- package/esm/errors/email-not-verified.error.d.mts.map +1 -0
- package/esm/errors/email-not-verified.error.mjs +20 -0
- package/esm/errors/email-not-verified.error.mjs.map +1 -0
- package/esm/errors/index.d.mts +7 -0
- package/esm/errors/index.mjs +9 -0
- package/esm/errors/invalid-one-time-token.error.d.mts +17 -0
- package/esm/errors/invalid-one-time-token.error.d.mts.map +1 -0
- package/esm/errors/invalid-one-time-token.error.mjs +21 -0
- package/esm/errors/invalid-one-time-token.error.mjs.map +1 -0
- package/esm/errors/invalid-passkey.error.d.mts +18 -0
- package/esm/errors/invalid-passkey.error.d.mts.map +1 -0
- package/esm/errors/invalid-passkey.error.mjs +23 -0
- package/esm/errors/invalid-passkey.error.mjs.map +1 -0
- package/esm/errors/invalid-provider-callback.error.d.mts +18 -0
- package/esm/errors/invalid-provider-callback.error.d.mts.map +1 -0
- package/esm/errors/invalid-provider-callback.error.mjs +23 -0
- package/esm/errors/invalid-provider-callback.error.mjs.map +1 -0
- package/esm/errors/notifications-unavailable.error.d.mts +15 -0
- package/esm/errors/notifications-unavailable.error.d.mts.map +1 -0
- package/esm/errors/notifications-unavailable.error.mjs +20 -0
- package/esm/errors/notifications-unavailable.error.mjs.map +1 -0
- package/esm/errors/provider-email-not-verified.error.d.mts +16 -0
- package/esm/errors/provider-email-not-verified.error.d.mts.map +1 -0
- package/esm/errors/provider-email-not-verified.error.mjs +20 -0
- package/esm/errors/provider-email-not-verified.error.mjs.map +1 -0
- package/esm/index.d.mts +31 -3
- package/esm/index.mjs +29 -1
- package/esm/middleware/auth.middleware.d.mts.map +1 -1
- package/esm/middleware/auth.middleware.mjs +13 -2
- package/esm/middleware/auth.middleware.mjs.map +1 -1
- package/esm/middleware/csrf-origin-check.d.mts +40 -0
- package/esm/middleware/csrf-origin-check.d.mts.map +1 -0
- package/esm/middleware/csrf-origin-check.mjs +105 -0
- package/esm/middleware/csrf-origin-check.mjs.map +1 -0
- package/esm/middleware/index.d.mts +5 -1
- package/esm/middleware/index.mjs +4 -0
- package/esm/middleware/one-time-token-throttle.middleware.d.mts +36 -0
- package/esm/middleware/one-time-token-throttle.middleware.d.mts.map +1 -0
- package/esm/middleware/one-time-token-throttle.middleware.mjs +53 -0
- package/esm/middleware/one-time-token-throttle.middleware.mjs.map +1 -0
- package/esm/middleware/otp-throttle.middleware.d.mts +30 -0
- package/esm/middleware/otp-throttle.middleware.d.mts.map +1 -0
- package/esm/middleware/otp-throttle.middleware.mjs +45 -0
- package/esm/middleware/otp-throttle.middleware.mjs.map +1 -0
- package/esm/middleware/require-verified-email.middleware.d.mts +18 -0
- package/esm/middleware/require-verified-email.middleware.d.mts.map +1 -0
- package/esm/middleware/require-verified-email.middleware.mjs +24 -0
- package/esm/middleware/require-verified-email.middleware.mjs.map +1 -0
- package/esm/models/access-token/access-token.model.d.mts +8 -0
- package/esm/models/access-token/access-token.model.d.mts.map +1 -1
- package/esm/models/access-token/access-token.model.mjs +3 -0
- package/esm/models/access-token/access-token.model.mjs.map +1 -1
- package/esm/models/index.d.mts +3 -1
- package/esm/models/index.d.mts.map +1 -1
- package/esm/models/index.mjs +16 -1
- package/esm/models/index.mjs.map +1 -1
- package/esm/models/one-time-token/index.d.mts +1 -0
- package/esm/models/one-time-token/index.mjs +3 -0
- package/esm/models/one-time-token/migration.mjs +31 -0
- package/esm/models/one-time-token/migration.mjs.map +1 -0
- package/esm/models/one-time-token/one-time-token.model.d.mts +173 -0
- package/esm/models/one-time-token/one-time-token.model.d.mts.map +1 -0
- package/esm/models/one-time-token/one-time-token.model.mjs +151 -0
- package/esm/models/one-time-token/one-time-token.model.mjs.map +1 -0
- package/esm/models/passkey-credential/index.d.mts +1 -0
- package/esm/models/passkey-credential/index.mjs +3 -0
- package/esm/models/passkey-credential/migration.mjs +25 -0
- package/esm/models/passkey-credential/migration.mjs.map +1 -0
- package/esm/models/passkey-credential/passkey-credential.model.d.mts +108 -0
- package/esm/models/passkey-credential/passkey-credential.model.d.mts.map +1 -0
- package/esm/models/passkey-credential/passkey-credential.model.mjs +70 -0
- package/esm/models/passkey-credential/passkey-credential.model.mjs.map +1 -0
- package/esm/models/provider-account/index.d.mts +1 -0
- package/esm/models/provider-account/index.mjs +3 -0
- package/esm/models/provider-account/migration.mjs +25 -0
- package/esm/models/provider-account/migration.mjs.map +1 -0
- package/esm/models/provider-account/provider-account.model.d.mts +75 -0
- package/esm/models/provider-account/provider-account.model.d.mts.map +1 -0
- package/esm/models/provider-account/provider-account.model.mjs +55 -0
- package/esm/models/provider-account/provider-account.model.mjs.map +1 -0
- package/esm/models/refresh-token/refresh-token.model.d.mts +7 -0
- package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
- package/esm/models/refresh-token/refresh-token.model.mjs +3 -0
- package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
- package/esm/otp/index.d.mts +1 -0
- package/esm/otp/index.mjs +3 -0
- package/esm/otp/otp.d.mts +38 -0
- package/esm/otp/otp.d.mts.map +1 -0
- package/esm/otp/otp.mjs +105 -0
- package/esm/otp/otp.mjs.map +1 -0
- package/esm/passkeys/index.d.mts +3 -0
- package/esm/passkeys/index.mjs +4 -0
- package/esm/passkeys/passkey-authentication.d.mts +33 -0
- package/esm/passkeys/passkey-authentication.d.mts.map +1 -0
- package/esm/passkeys/passkey-authentication.mjs +93 -0
- package/esm/passkeys/passkey-authentication.mjs.map +1 -0
- package/esm/passkeys/passkey-challenges.mjs +53 -0
- package/esm/passkeys/passkey-challenges.mjs.map +1 -0
- package/esm/passkeys/passkey-registration.d.mts +29 -0
- package/esm/passkeys/passkey-registration.d.mts.map +1 -0
- package/esm/passkeys/passkey-registration.mjs +88 -0
- package/esm/passkeys/passkey-registration.mjs.map +1 -0
- package/esm/passkeys/simplewebauthn.d.mts +21 -0
- package/esm/passkeys/simplewebauthn.d.mts.map +1 -0
- package/esm/passkeys/simplewebauthn.mjs +13 -0
- package/esm/passkeys/simplewebauthn.mjs.map +1 -0
- package/esm/providers/google-provider.d.mts +40 -0
- package/esm/providers/google-provider.d.mts.map +1 -0
- package/esm/providers/google-provider.mjs +105 -0
- package/esm/providers/google-provider.mjs.map +1 -0
- package/esm/providers/index.d.mts +2 -0
- package/esm/providers/index.mjs +4 -0
- package/esm/providers/provider-login.d.mts +43 -0
- package/esm/providers/provider-login.d.mts.map +1 -0
- package/esm/providers/provider-login.mjs +86 -0
- package/esm/providers/provider-login.mjs.map +1 -0
- package/esm/providers/provider-state-cookie.mjs +63 -0
- package/esm/providers/provider-state-cookie.mjs.map +1 -0
- package/esm/providers/resolve-provider-user.mjs +45 -0
- package/esm/providers/resolve-provider-user.mjs.map +1 -0
- package/esm/services/auth-config.mjs +84 -0
- package/esm/services/auth-config.mjs.map +1 -1
- package/esm/services/auth.service.d.mts +59 -2
- package/esm/services/auth.service.d.mts.map +1 -1
- package/esm/services/auth.service.mjs +86 -1
- package/esm/services/auth.service.mjs.map +1 -1
- package/esm/services/current-user.d.mts +21 -0
- package/esm/services/current-user.d.mts.map +1 -0
- package/esm/services/current-user.mjs +24 -0
- package/esm/services/current-user.mjs.map +1 -0
- package/esm/services/email-verification.d.mts +35 -0
- package/esm/services/email-verification.d.mts.map +1 -0
- package/esm/services/email-verification.mjs +55 -0
- package/esm/services/email-verification.mjs.map +1 -0
- package/esm/services/index.d.mts +5 -1
- package/esm/services/index.mjs +4 -0
- package/esm/services/one-time-tokens.d.mts +32 -0
- package/esm/services/one-time-tokens.d.mts.map +1 -0
- package/esm/services/one-time-tokens.mjs +56 -0
- package/esm/services/one-time-tokens.mjs.map +1 -0
- package/esm/services/optional-peer.mjs +37 -0
- package/esm/services/optional-peer.mjs.map +1 -0
- package/esm/services/password-reset.d.mts +34 -0
- package/esm/services/password-reset.d.mts.map +1 -0
- package/esm/services/password-reset.mjs +81 -0
- package/esm/services/password-reset.mjs.map +1 -0
- package/esm/services/token-notifications.mjs +87 -0
- package/esm/services/token-notifications.mjs.map +1 -0
- package/esm/utils/auth-error-codes.d.mts +48 -1
- package/esm/utils/auth-error-codes.d.mts.map +1 -1
- package/esm/utils/auth-error-codes.mjs +41 -0
- package/esm/utils/auth-error-codes.mjs.map +1 -1
- package/esm/utils/index.mjs +1 -0
- package/esm/utils/safe-equal.d.mts +6 -0
- package/esm/utils/safe-equal.d.mts.map +1 -0
- package/esm/utils/safe-equal.mjs +13 -0
- package/esm/utils/safe-equal.mjs.map +1 -0
- package/llms-full.txt +402 -29
- package/llms.txt +2 -0
- package/package.json +21 -7
- package/skills/handle-login-and-logout/SKILL.md +1 -1
- package/skills/login-with-providers/SKILL.md +154 -0
- package/skills/manage-tokens/SKILL.md +7 -1
- package/skills/overview/SKILL.md +1 -1
- package/skills/protect-routes/SKILL.md +71 -13
- package/skills/register-user/SKILL.md +6 -8
- package/skills/run-auth-commands/SKILL.md +1 -1
- 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,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,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";
|