@warlock.js/auth 5.18.0 → 5.19.1
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 +17 -0
- package/esm/contracts/index.d.mts +1 -1
- package/esm/contracts/types.d.mts +23 -2
- package/esm/contracts/types.d.mts.map +1 -1
- package/esm/contracts/types.mjs.map +1 -1
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +2 -1
- package/esm/middleware/auth.middleware.d.mts +9 -6
- package/esm/middleware/auth.middleware.d.mts.map +1 -1
- package/esm/middleware/auth.middleware.mjs +101 -80
- package/esm/middleware/auth.middleware.mjs.map +1 -1
- package/esm/models/access-token/access-token.model.d.mts +31 -7
- package/esm/models/access-token/access-token.model.d.mts.map +1 -1
- package/esm/models/access-token/access-token.model.mjs +21 -1
- package/esm/models/access-token/access-token.model.mjs.map +1 -1
- package/esm/models/access-token/family-association.migration.mjs +18 -0
- package/esm/models/access-token/family-association.migration.mjs.map +1 -0
- package/esm/models/auth-token-family/auth-token-family.model.d.mts +90 -0
- package/esm/models/auth-token-family/auth-token-family.model.d.mts.map +1 -0
- package/esm/models/auth-token-family/auth-token-family.model.mjs +80 -0
- package/esm/models/auth-token-family/auth-token-family.model.mjs.map +1 -0
- package/esm/models/auth-token-family/index.d.mts +1 -0
- package/esm/models/auth-token-family/index.mjs +3 -0
- package/esm/models/auth-token-family/migration.mjs +25 -0
- package/esm/models/auth-token-family/migration.mjs.map +1 -0
- package/esm/models/index.d.mts +1 -0
- package/esm/models/index.d.mts.map +1 -1
- package/esm/models/index.mjs +8 -0
- package/esm/models/index.mjs.map +1 -1
- package/esm/models/refresh-token/refresh-token.model.d.mts +43 -0
- package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
- package/esm/models/refresh-token/refresh-token.model.mjs +17 -0
- package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
- package/esm/models/refresh-token/successor-association.migration.mjs +22 -0
- package/esm/models/refresh-token/successor-association.migration.mjs.map +1 -0
- package/esm/services/auth.service.d.mts +22 -1
- package/esm/services/auth.service.d.mts.map +1 -1
- package/esm/services/auth.service.mjs +216 -32
- package/esm/services/auth.service.mjs.map +1 -1
- package/esm/services/token-family-operation.mjs +74 -0
- package/esm/services/token-family-operation.mjs.map +1 -0
- package/llms-full.txt +63 -58
- package/llms.txt +9 -9
- package/package.json +8 -8
- package/skills/auth-basics/SKILL.md +3 -2
- package/skills/customize-token-storage/SKILL.md +1 -1
- package/skills/customize-user-type/SKILL.md +1 -1
- package/skills/handle-login-and-logout/SKILL.md +1 -1
- package/skills/login-with-providers/SKILL.md +1 -1
- package/skills/manage-tokens/SKILL.md +19 -6
- package/skills/overview/SKILL.md +3 -3
- package/skills/protect-routes/SKILL.md +30 -39
- package/skills/register-user/SKILL.md +1 -1
- package/skills/run-auth-commands/SKILL.md +1 -1
- package/skills/throttle-login-attempts/SKILL.md +1 -1
- package/skills/verify-email-and-reset-password/SKILL.md +1 -1
|
@@ -13,17 +13,26 @@ import { Model } from "@warlock.js/cascade";
|
|
|
13
13
|
declare const accessTokenSchema: import("@warlock.js/seal").ObjectValidator<{
|
|
14
14
|
token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
15
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>;
|
|
16
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>; /** Null for tokens issued before family tracking was introduced. */
|
|
17
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
18
|
+
isOptional: true;
|
|
19
|
+
};
|
|
17
20
|
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
18
21
|
}> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
|
|
19
22
|
token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
20
23
|
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
21
|
-
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
24
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>; /** Null for tokens issued before family tracking was introduced. */
|
|
25
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
26
|
+
isOptional: true;
|
|
27
|
+
};
|
|
22
28
|
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
23
29
|
}>, import("@warlock.js/seal").InferInputObjectShape<{
|
|
24
30
|
token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
25
31
|
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>;
|
|
32
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>; /** Null for tokens issued before family tracking was introduced. */
|
|
33
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
34
|
+
isOptional: true;
|
|
35
|
+
};
|
|
27
36
|
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
28
37
|
}>>;
|
|
29
38
|
/**
|
|
@@ -41,17 +50,26 @@ declare class AccessToken extends Model {
|
|
|
41
50
|
static schema: import("@warlock.js/seal").ObjectValidator<{
|
|
42
51
|
token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
43
52
|
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
44
|
-
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
53
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>; /** Null for tokens issued before family tracking was introduced. */
|
|
54
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
55
|
+
isOptional: true;
|
|
56
|
+
};
|
|
45
57
|
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
46
58
|
}> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
|
|
47
59
|
token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
48
60
|
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
49
|
-
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
61
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>; /** Null for tokens issued before family tracking was introduced. */
|
|
62
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
63
|
+
isOptional: true;
|
|
64
|
+
};
|
|
50
65
|
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
51
66
|
}>, import("@warlock.js/seal").InferInputObjectShape<{
|
|
52
67
|
token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
53
68
|
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
54
|
-
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
69
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>; /** Null for tokens issued before family tracking was introduced. */
|
|
70
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
71
|
+
isOptional: true;
|
|
72
|
+
};
|
|
55
73
|
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
56
74
|
}>>;
|
|
57
75
|
/**
|
|
@@ -99,7 +117,7 @@ declare class AccessToken extends Model {
|
|
|
99
117
|
/**
|
|
100
118
|
* Persist a freshly-signed access token for the user.
|
|
101
119
|
*/
|
|
102
|
-
static issue(user: Auth, token: string, expiresAt: Date): Promise<AccessToken>;
|
|
120
|
+
static issue(user: Auth, token: string, expiresAt: Date, familyId?: string): Promise<AccessToken>;
|
|
103
121
|
/**
|
|
104
122
|
* Find an access-token row by its raw token string.
|
|
105
123
|
*/
|
|
@@ -112,6 +130,12 @@ declare class AccessToken extends Model {
|
|
|
112
130
|
* Delete every access token belonging to the user.
|
|
113
131
|
*/
|
|
114
132
|
static deleteAllForUser(user: Auth): Promise<number>;
|
|
133
|
+
/**
|
|
134
|
+
* Delete every access token attached to one family, plus pre-family rows for
|
|
135
|
+
* the same user type. The latter is deliberately conservative during the
|
|
136
|
+
* additive upgrade: old access rows cannot be assigned safely to a family.
|
|
137
|
+
*/
|
|
138
|
+
static deleteFamilyAndLegacyForUser(user: Auth, familyId: string): Promise<void>;
|
|
115
139
|
/**
|
|
116
140
|
* Hard-delete every expired access-token row. Returns the number removed.
|
|
117
141
|
* Runs from the `auth.cleanup` CLI command (a cold batch path).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-token.model.d.mts","names":[],"sources":["../../../../../../../../auth/src/models/access-token/access-token.model.ts"],"mappings":";;;;;;AAaA;;;;;;cAAa,iBAAA,6BAAiB,eAAA
|
|
1
|
+
{"version":3,"file":"access-token.model.d.mts","names":[],"sources":["../../../../../../../../auth/src/models/access-token/access-token.model.ts"],"mappings":";;;;;;AAaA;;;;;;cAAa,iBAAA,6BAAiB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmBjB,WAAA,SAAoB,KAAA;EAAA,OACjB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAQN,MAAA;EAyD4C;;;;;;;EAAA,OAhD5C,cAAA;EA2E2B;EAAA,IAxE9B,MAAA;EAiFqE;EAAA,IA5ErE,QAAA;EA0GsC;;;;;;;;;;;;;;;EAAA,IAvFtC,SAAA;;;;;;;MAcA,YAAA;;;;SAOG,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,SAAA,EAAW,IAAA,EAAM,QAAA,YAAiB,OAAA,CAAA,WAAA;;;;SAanE,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,WAAA;;;;SAOpC,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA,WAAa,OAAA;;;;SAOvC,gBAAA,CAAiB,IAAA,EAAM,IAAA,GAAI,OAAA;;;;;;SASrB,4BAAA,CAA6B,IAAA,EAAM,IAAA,EAAM,QAAA,WAAmB,OAAA;;;;;SAS5D,YAAA,IAAgB,OAAA;;;;;;;;;;;;SAqBhB,iBAAA,IAAqB,OAAA,CAAQ,WAAA;;;;;;SAW7B,kBAAA,IAAsB,OAAA,CAAQ,WAAA;AAAA"}
|
|
@@ -15,6 +15,8 @@ const accessTokenSchema = v.object({
|
|
|
15
15
|
token: v.string().required(),
|
|
16
16
|
user_id: v.scalar().required(),
|
|
17
17
|
user_type: v.string().required(),
|
|
18
|
+
/** Null for tokens issued before family tracking was introduced. */
|
|
19
|
+
family_id: v.string().optional(),
|
|
18
20
|
expires_at: v.date().required()
|
|
19
21
|
});
|
|
20
22
|
/**
|
|
@@ -80,11 +82,12 @@ var AccessToken = class extends Model {
|
|
|
80
82
|
/**
|
|
81
83
|
* Persist a freshly-signed access token for the user.
|
|
82
84
|
*/
|
|
83
|
-
static issue(user, token, expiresAt) {
|
|
85
|
+
static issue(user, token, expiresAt, familyId) {
|
|
84
86
|
return this.create({
|
|
85
87
|
token,
|
|
86
88
|
user_id: user.id,
|
|
87
89
|
user_type: user.userType,
|
|
90
|
+
family_id: familyId,
|
|
88
91
|
expires_at: expiresAt
|
|
89
92
|
});
|
|
90
93
|
}
|
|
@@ -110,6 +113,23 @@ var AccessToken = class extends Model {
|
|
|
110
113
|
return this.delete({ user_id: user.id });
|
|
111
114
|
}
|
|
112
115
|
/**
|
|
116
|
+
* Delete every access token attached to one family, plus pre-family rows for
|
|
117
|
+
* the same user type. The latter is deliberately conservative during the
|
|
118
|
+
* additive upgrade: old access rows cannot be assigned safely to a family.
|
|
119
|
+
*/
|
|
120
|
+
static async deleteFamilyAndLegacyForUser(user, familyId) {
|
|
121
|
+
await this.delete({
|
|
122
|
+
user_id: user.id,
|
|
123
|
+
user_type: user.userType,
|
|
124
|
+
family_id: familyId
|
|
125
|
+
});
|
|
126
|
+
await this.delete({
|
|
127
|
+
user_id: user.id,
|
|
128
|
+
user_type: user.userType,
|
|
129
|
+
family_id: null
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
113
133
|
* Hard-delete every expired access-token row. Returns the number removed.
|
|
114
134
|
* Runs from the `auth.cleanup` CLI command (a cold batch path).
|
|
115
135
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-token.model.mjs","names":[],"sources":["../../../../../../../../auth/src/models/access-token/access-token.model.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\r\nimport { v } from \"@warlock.js/seal\";\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 access-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 * `last_access` and the `is_active` soft-revoke flag were removed — neither was\r\n * ever read; access tokens are revoked by deleting the row.\r\n */\r\nexport const accessTokenSchema = v.object({\r\n token: v.string().required(),\r\n user_id: v.scalar().required(),\r\n user_type: v.string().required(),\r\n expires_at: v.date().required(),\r\n});\r\n\r\n/**\r\n * Persisted access-token record + the data layer for access tokens.\r\n *\r\n * **Role.** Owns access-token persistence and lookup. The middleware checks a\r\n * presented JWT against this table so deleting a row (logout) invalidates the\r\n * token immediately, before its JWT expiry. The auth service goes through the\r\n * named statics exclusively, so it never hard-codes a column name and an\r\n * override can rename/add columns by registering under\r\n * `config.auth.accessToken.model`.\r\n */\r\nexport class AccessToken extends Model {\r\n public static table = \"access_tokens\";\r\n\r\n public static schema = accessTokenSchema;\r\n\r\n /**\r\n * The raw signed JWT is a bearer credential — never serialize it. `hidden`\r\n * only strips it from `toJSON()` / `JSON.stringify(model)` (so\r\n * `response.json(token)` or a resource cannot leak it); `get(\"token\")`,\r\n * `findByToken` and every query still read the column.\r\n */\r\n public static hidden = [\"token\"];\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 `access_tokensTrash` on every `destroy()` (logout, expiry purge,\r\n * never-expiring purge). A revoked/expired access token must not survive\r\n * anywhere.\r\n */\r\n public static deleteStrategy = \"permanent\" as const;\r\n\r\n /** The user this token was issued for. */\r\n public get userId() {\r\n return this.get(\"user_id\");\r\n }\r\n\r\n /** The user-type slug this token was issued for. */\r\n public get userType(): string {\r\n return this.get(\"user_type\");\r\n }\r\n\r\n /**\r\n * Whether the persisted expiry has passed.\r\n *\r\n * The middleware asks this on every request, which is what makes the row the\r\n * authority it always looked like it was: before 4.12.0 the gate only checked\r\n * that the row *existed*, so a row the database knew was dead still let its\r\n * token through — the database was never asked.\r\n *\r\n * **Fails closed.** A missing or unparseable `expires_at` counts as expired.\r\n * `expires_at` is `required` in the schema, so a row that cannot answer \"when\r\n * does this die\" is malformed, and the safe reading of a malformed credential\r\n * is that it is not one. This is deliberately stricter than a naive\r\n * `now > expires_at`, which answers `false` for an `Invalid Date` and thereby\r\n * grants exactly the poisoned rows an unlimited life.\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(expiresAt).getTime() <= Date.now();\r\n }\r\n\r\n /**\r\n * Whether nothing about this row can ever retire it — an unusable\r\n * `expires_at`, or a token carrying no `exp` claim. See\r\n * {@link isNeverExpiring}; this is the predicate `purgeNeverExpiring` selects\r\n * on.\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 /**\r\n * Persist a freshly-signed access token for the user.\r\n */\r\n public static issue(user: Auth, token: string, expiresAt: Date) {\r\n return this.create({\r\n token,\r\n user_id: user.id,\r\n user_type: user.userType,\r\n expires_at: expiresAt,\r\n });\r\n }\r\n\r\n /**\r\n * Find an access-token row by its raw token string.\r\n */\r\n public static findByToken(token: string): Promise<AccessToken | null> {\r\n return this.first({ token });\r\n }\r\n\r\n /**\r\n * Delete a specific token that belongs to the given 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 * Delete every access token belonging to the user.\r\n */\r\n public static deleteAllForUser(user: Auth) {\r\n return this.delete({ user_id: user.id });\r\n }\r\n\r\n /**\r\n * Hard-delete every expired access-token row. Returns the number removed.\r\n * Runs from the `auth.cleanup` CLI command (a cold batch path).\r\n */\r\n public static async purgeExpired(): Promise<number> {\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.length;\r\n }\r\n\r\n /**\r\n * Every row that can never retire itself — see {@link neverExpires}.\r\n *\r\n * **A full scan, filtered in memory, on purpose.** The defining case is a row\r\n * whose `expires_at` is an `Invalid Date`, which no date predicate can select\r\n * (`< now` and `> now` are both `false` for it), and the definitive case is a\r\n * token with no `exp` claim, which lives inside the token string rather than\r\n * in a column. Neither is expressible as a `where`, so the rows have to be\r\n * read to be judged. This runs from a one-off remediation command, not a\r\n * request path.\r\n */\r\n public static async findNeverExpiring(): Promise<AccessToken[]> {\r\n const tokens = await this.query().get();\r\n\r\n return tokens.filter((token: AccessToken) => token.neverExpires);\r\n }\r\n\r\n /**\r\n * Hard-delete every never-expiring row, returning the rows removed so the\r\n * caller can report what it revoked. Deletion *is* revocation for access\r\n * tokens — the middleware rejects a token with no row.\r\n */\r\n public static async purgeNeverExpiring(): Promise<AccessToken[]> {\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":";;;;;;;;;;;;;AAaA,MAAa,oBAAoB,EAAE,OAAO;CACxC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,YAAY,EAAE,KAAK,CAAC,CAAC,SAAS;AAChC,CAAC;;;;;;;;;;;AAYD,IAAa,cAAb,cAAiC,MAAM;;eACf;;;gBAEC;;;gBAQA,CAAC,OAAO;;;wBASA;;;CAG/B,IAAW,SAAS;EAClB,OAAO,KAAK,IAAI,SAAS;CAC3B;;CAGA,IAAW,WAAmB;EAC5B,OAAO,KAAK,IAAI,WAAW;CAC7B;;;;;;;;;;;;;;;;CAiBA,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;;;;;;;CAQA,IAAW,eAAwB;EACjC,OAAO,gBAAgB,KAAK,IAAI,OAAO,GAAG,KAAK,IAAI,YAAY,CAAC;CAClE;;;;CAKA,OAAc,MAAM,MAAY,OAAe,WAAiB;
|
|
1
|
+
{"version":3,"file":"access-token.model.mjs","names":[],"sources":["../../../../../../../../auth/src/models/access-token/access-token.model.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\r\nimport { v } from \"@warlock.js/seal\";\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 access-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 * `last_access` and the `is_active` soft-revoke flag were removed — neither was\r\n * ever read; access tokens are revoked by deleting the row.\r\n */\r\nexport const accessTokenSchema = v.object({\r\n token: v.string().required(),\r\n user_id: v.scalar().required(),\r\n user_type: v.string().required(),\r\n /** Null for tokens issued before family tracking was introduced. */\r\n family_id: v.string().optional(),\r\n expires_at: v.date().required(),\r\n});\r\n\r\n/**\r\n * Persisted access-token record + the data layer for access tokens.\r\n *\r\n * **Role.** Owns access-token persistence and lookup. The middleware checks a\r\n * presented JWT against this table so deleting a row (logout) invalidates the\r\n * token immediately, before its JWT expiry. The auth service goes through the\r\n * named statics exclusively, so it never hard-codes a column name and an\r\n * override can rename/add columns by registering under\r\n * `config.auth.accessToken.model`.\r\n */\r\nexport class AccessToken extends Model {\r\n public static table = \"access_tokens\";\r\n\r\n public static schema = accessTokenSchema;\r\n\r\n /**\r\n * The raw signed JWT is a bearer credential — never serialize it. `hidden`\r\n * only strips it from `toJSON()` / `JSON.stringify(model)` (so\r\n * `response.json(token)` or a resource cannot leak it); `get(\"token\")`,\r\n * `findByToken` and every query still read the column.\r\n */\r\n public static hidden = [\"token\"];\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 `access_tokensTrash` on every `destroy()` (logout, expiry purge,\r\n * never-expiring purge). A revoked/expired access token must not survive\r\n * anywhere.\r\n */\r\n public static deleteStrategy = \"permanent\" as const;\r\n\r\n /** The user this token was issued for. */\r\n public get userId() {\r\n return this.get(\"user_id\");\r\n }\r\n\r\n /** The user-type slug this token was issued for. */\r\n public get userType(): string {\r\n return this.get(\"user_type\");\r\n }\r\n\r\n /**\r\n * Whether the persisted expiry has passed.\r\n *\r\n * The middleware asks this on every request, which is what makes the row the\r\n * authority it always looked like it was: before 4.12.0 the gate only checked\r\n * that the row *existed*, so a row the database knew was dead still let its\r\n * token through — the database was never asked.\r\n *\r\n * **Fails closed.** A missing or unparseable `expires_at` counts as expired.\r\n * `expires_at` is `required` in the schema, so a row that cannot answer \"when\r\n * does this die\" is malformed, and the safe reading of a malformed credential\r\n * is that it is not one. This is deliberately stricter than a naive\r\n * `now > expires_at`, which answers `false` for an `Invalid Date` and thereby\r\n * grants exactly the poisoned rows an unlimited life.\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(expiresAt).getTime() <= Date.now();\r\n }\r\n\r\n /**\r\n * Whether nothing about this row can ever retire it — an unusable\r\n * `expires_at`, or a token carrying no `exp` claim. See\r\n * {@link isNeverExpiring}; this is the predicate `purgeNeverExpiring` selects\r\n * on.\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 /**\r\n * Persist a freshly-signed access token for the user.\r\n */\r\n public static issue(user: Auth, token: string, expiresAt: Date, familyId?: string) {\r\n return this.create({\r\n token,\r\n user_id: user.id,\r\n user_type: user.userType,\r\n family_id: familyId,\r\n expires_at: expiresAt,\r\n });\r\n }\r\n\r\n /**\r\n * Find an access-token row by its raw token string.\r\n */\r\n public static findByToken(token: string): Promise<AccessToken | null> {\r\n return this.first({ token });\r\n }\r\n\r\n /**\r\n * Delete a specific token that belongs to the given 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 * Delete every access token belonging to the user.\r\n */\r\n public static deleteAllForUser(user: Auth) {\r\n return this.delete({ user_id: user.id });\r\n }\r\n\r\n /**\r\n * Delete every access token attached to one family, plus pre-family rows for\r\n * the same user type. The latter is deliberately conservative during the\r\n * additive upgrade: old access rows cannot be assigned safely to a family.\r\n */\r\n public static async deleteFamilyAndLegacyForUser(user: Auth, familyId: string): Promise<void> {\r\n await this.delete({ user_id: user.id, user_type: user.userType, family_id: familyId });\r\n await this.delete({ user_id: user.id, user_type: user.userType, family_id: null });\r\n }\r\n\r\n /**\r\n * Hard-delete every expired access-token row. Returns the number removed.\r\n * Runs from the `auth.cleanup` CLI command (a cold batch path).\r\n */\r\n public static async purgeExpired(): Promise<number> {\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.length;\r\n }\r\n\r\n /**\r\n * Every row that can never retire itself — see {@link neverExpires}.\r\n *\r\n * **A full scan, filtered in memory, on purpose.** The defining case is a row\r\n * whose `expires_at` is an `Invalid Date`, which no date predicate can select\r\n * (`< now` and `> now` are both `false` for it), and the definitive case is a\r\n * token with no `exp` claim, which lives inside the token string rather than\r\n * in a column. Neither is expressible as a `where`, so the rows have to be\r\n * read to be judged. This runs from a one-off remediation command, not a\r\n * request path.\r\n */\r\n public static async findNeverExpiring(): Promise<AccessToken[]> {\r\n const tokens = await this.query().get();\r\n\r\n return tokens.filter((token: AccessToken) => token.neverExpires);\r\n }\r\n\r\n /**\r\n * Hard-delete every never-expiring row, returning the rows removed so the\r\n * caller can report what it revoked. Deletion *is* revocation for access\r\n * tokens — the middleware rejects a token with no row.\r\n */\r\n public static async purgeNeverExpiring(): Promise<AccessToken[]> {\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":";;;;;;;;;;;;;AAaA,MAAa,oBAAoB,EAAE,OAAO;CACxC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;;CAE/B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,YAAY,EAAE,KAAK,CAAC,CAAC,SAAS;AAChC,CAAC;;;;;;;;;;;AAYD,IAAa,cAAb,cAAiC,MAAM;;eACf;;;gBAEC;;;gBAQA,CAAC,OAAO;;;wBASA;;;CAG/B,IAAW,SAAS;EAClB,OAAO,KAAK,IAAI,SAAS;CAC3B;;CAGA,IAAW,WAAmB;EAC5B,OAAO,KAAK,IAAI,WAAW;CAC7B;;;;;;;;;;;;;;;;CAiBA,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;;;;;;;CAQA,IAAW,eAAwB;EACjC,OAAO,gBAAgB,KAAK,IAAI,OAAO,GAAG,KAAK,IAAI,YAAY,CAAC;CAClE;;;;CAKA,OAAc,MAAM,MAAY,OAAe,WAAiB,UAAmB;EACjF,OAAO,KAAK,OAAO;GACjB;GACA,SAAS,KAAK;GACd,WAAW,KAAK;GAChB,WAAW;GACX,YAAY;EACd,CAAC;CACH;;;;CAKA,OAAc,YAAY,OAA4C;EACpE,OAAO,KAAK,MAAM,EAAE,MAAM,CAAC;CAC7B;;;;CAKA,OAAc,cAAc,MAAY,OAAe;EACrD,OAAO,KAAK,OAAO;GAAE;GAAO,SAAS,KAAK;EAAG,CAAC;CAChD;;;;CAKA,OAAc,iBAAiB,MAAY;EACzC,OAAO,KAAK,OAAO,EAAE,SAAS,KAAK,GAAG,CAAC;CACzC;;;;;;CAOA,aAAoB,6BAA6B,MAAY,UAAiC;EAC5F,MAAM,KAAK,OAAO;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;GAAU,WAAW;EAAS,CAAC;EACrF,MAAM,KAAK,OAAO;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;GAAU,WAAW;EAAK,CAAC;CACnF;;;;;CAMA,aAAoB,eAAgC;EAClD,MAAM,gBAAgB,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,cAAc,qBAAK,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI;EAElF,KAAK,MAAM,SAAS,eAClB,MAAM,MAAM,QAAQ;EAGtB,OAAO,cAAc;CACvB;;;;;;;;;;;;CAaA,aAAoB,oBAA4C;EAG9D,QAAO,MAFc,KAAK,MAAM,CAAC,CAAC,IAAI,EAEzB,CAAC,QAAQ,UAAuB,MAAM,YAAY;CACjE;;;;;;CAOA,aAAoB,qBAA6C;EAC/D,MAAM,SAAS,MAAM,KAAK,kBAAkB;EAE5C,KAAK,MAAM,SAAS,QAClB,MAAM,MAAM,QAAQ;EAGtB,OAAO;CACT;AACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AccessToken } from "./access-token.model.mjs";
|
|
2
|
+
import { migrate } from "@warlock.js/cascade";
|
|
3
|
+
|
|
4
|
+
//#region ../auth/src/models/access-token/family-association.migration.ts
|
|
5
|
+
/** Adds an optional family association without invalidating existing access tokens. */
|
|
6
|
+
const AccessTokenFamilyAssociationMigration = migrate(AccessToken, {
|
|
7
|
+
name: "accessTokenFamilyAssociation",
|
|
8
|
+
up() {
|
|
9
|
+
this.string("family_id", 64).nullable().index();
|
|
10
|
+
},
|
|
11
|
+
down() {
|
|
12
|
+
this.dropColumn("family_id");
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { AccessTokenFamilyAssociationMigration };
|
|
18
|
+
//# sourceMappingURL=family-association.migration.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"family-association.migration.mjs","names":[],"sources":["../../../../../../../../auth/src/models/access-token/family-association.migration.ts"],"sourcesContent":["import { migrate } from \"@warlock.js/cascade\";\nimport { AccessToken } from \"./access-token.model\";\n\n/** Adds an optional family association without invalidating existing access tokens. */\nexport const AccessTokenFamilyAssociationMigration = migrate(AccessToken, {\n name: \"accessTokenFamilyAssociation\",\n up() {\n this.string(\"family_id\", 64).nullable().index();\n },\n down() {\n this.dropColumn(\"family_id\");\n },\n});\n"],"mappings":";;;;;AAIA,MAAa,wCAAwC,QAAQ,aAAa;CACxE,MAAM;CACN,KAAK;EACH,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM;CAChD;CACA,OAAO;EACL,KAAK,WAAW,WAAW;CAC7B;AACF,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Auth } from "../auth.model.mjs";
|
|
2
|
+
import { Model } from "@warlock.js/cascade";
|
|
3
|
+
|
|
4
|
+
//#region ../auth/src/models/auth-token-family/auth-token-family.model.d.ts
|
|
5
|
+
/** Durable revision authority for one refresh-token rotation family. */
|
|
6
|
+
declare const authTokenFamilySchema: import("@warlock.js/seal").ObjectValidator<{
|
|
7
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
8
|
+
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
9
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
10
|
+
revision: import("@warlock.js/seal").NumberValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
|
|
11
|
+
hasDefault: true;
|
|
12
|
+
};
|
|
13
|
+
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
14
|
+
isOptional: true;
|
|
15
|
+
};
|
|
16
|
+
}> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
|
|
17
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
18
|
+
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
19
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
20
|
+
revision: import("@warlock.js/seal").NumberValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
|
|
21
|
+
hasDefault: true;
|
|
22
|
+
};
|
|
23
|
+
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
24
|
+
isOptional: true;
|
|
25
|
+
};
|
|
26
|
+
}>, import("@warlock.js/seal").InferInputObjectShape<{
|
|
27
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
28
|
+
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
29
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
30
|
+
revision: import("@warlock.js/seal").NumberValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
|
|
31
|
+
hasDefault: true;
|
|
32
|
+
};
|
|
33
|
+
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
34
|
+
isOptional: true;
|
|
35
|
+
};
|
|
36
|
+
}>>;
|
|
37
|
+
/** Coordinates family-wide refresh, replay containment, and logout transitions. */
|
|
38
|
+
declare class AuthTokenFamily extends Model {
|
|
39
|
+
static table: string;
|
|
40
|
+
static schema: import("@warlock.js/seal").ObjectValidator<{
|
|
41
|
+
family_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
|
+
revision: import("@warlock.js/seal").NumberValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
|
|
45
|
+
hasDefault: true;
|
|
46
|
+
};
|
|
47
|
+
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
48
|
+
isOptional: true;
|
|
49
|
+
};
|
|
50
|
+
}> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
|
|
51
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
52
|
+
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
53
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
54
|
+
revision: import("@warlock.js/seal").NumberValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
|
|
55
|
+
hasDefault: true;
|
|
56
|
+
};
|
|
57
|
+
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
58
|
+
isOptional: true;
|
|
59
|
+
};
|
|
60
|
+
}>, import("@warlock.js/seal").InferInputObjectShape<{
|
|
61
|
+
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
62
|
+
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
63
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
64
|
+
revision: import("@warlock.js/seal").NumberValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
|
|
65
|
+
hasDefault: true;
|
|
66
|
+
};
|
|
67
|
+
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
68
|
+
isOptional: true;
|
|
69
|
+
};
|
|
70
|
+
}>>;
|
|
71
|
+
/** Whether a family-wide revoke has made every member unusable. */
|
|
72
|
+
get isRevoked(): boolean;
|
|
73
|
+
/** Create the durable family record before its first token pair is issued. */
|
|
74
|
+
static issue(user: Auth, familyId: string): Promise<AuthTokenFamily>;
|
|
75
|
+
static findByFamilyId(familyId: string): Promise<AuthTokenFamily | null>;
|
|
76
|
+
/** Add a durable row for an existing lineage through a driver-native upsert. */
|
|
77
|
+
static ensure(user: Auth, familyId: string): Promise<AuthTokenFamily>;
|
|
78
|
+
/** Every non-revoked family for a user, including a rotated refresh row. */
|
|
79
|
+
static activeFor(user: Auth): Promise<AuthTokenFamily[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Compare-and-set the family revision. A zero result is a concurrent family
|
|
82
|
+
* transition, never a successful no-op.
|
|
83
|
+
*/
|
|
84
|
+
static advanceRevision(familyId: string, expectedRevision: number): Promise<number>;
|
|
85
|
+
/** Stamp the durable family revoke marker after a successful CAS. */
|
|
86
|
+
static revoke(familyId: string, expectedRevision: number): Promise<number>;
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { AuthTokenFamily, authTokenFamilySchema };
|
|
90
|
+
//# sourceMappingURL=auth-token-family.model.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-token-family.model.d.mts","names":[],"sources":["../../../../../../../../auth/src/models/auth-token-family/auth-token-family.model.ts"],"mappings":";;;;;cAKa,qBAAA,6BAAqB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cASrB,eAAA,SAAwB,KAAA;EAAA,OACrB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAGT,SAAA;;SAKG,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,QAAA,WAAgB,OAAA,CAAA,eAAA;EAAA,OASlC,cAAA,CAAe,QAAA,WAAmB,OAAA,CAAQ,eAAA;;SAKpC,MAAA,CAAO,IAAA,EAAM,IAAA,EAAM,QAAA,WAAmB,OAAA,CAAQ,eAAA;;SAmBpD,SAAA,CAAU,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,eAAA;;AA5C/C;;;SAsDgB,eAAA,CAAgB,QAAA,UAAkB,gBAAA,WAA2B,OAAA;;SAQ7D,MAAA,CAAO,QAAA,UAAkB,gBAAA,WAA2B,OAAA;AAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Model } from "@warlock.js/cascade";
|
|
2
|
+
import { v } from "@warlock.js/seal";
|
|
3
|
+
|
|
4
|
+
//#region ../auth/src/models/auth-token-family/auth-token-family.model.ts
|
|
5
|
+
/** Durable revision authority for one refresh-token rotation family. */
|
|
6
|
+
const authTokenFamilySchema = v.object({
|
|
7
|
+
family_id: v.string().required(),
|
|
8
|
+
user_id: v.scalar().required(),
|
|
9
|
+
user_type: v.string().required(),
|
|
10
|
+
revision: v.number().default(0),
|
|
11
|
+
revoked_at: v.date().optional()
|
|
12
|
+
});
|
|
13
|
+
/** Coordinates family-wide refresh, replay containment, and logout transitions. */
|
|
14
|
+
var AuthTokenFamily = class extends Model {
|
|
15
|
+
static {
|
|
16
|
+
this.table = "auth_token_families";
|
|
17
|
+
}
|
|
18
|
+
static {
|
|
19
|
+
this.schema = authTokenFamilySchema;
|
|
20
|
+
}
|
|
21
|
+
/** Whether a family-wide revoke has made every member unusable. */
|
|
22
|
+
get isRevoked() {
|
|
23
|
+
return Boolean(this.get("revoked_at"));
|
|
24
|
+
}
|
|
25
|
+
/** Create the durable family record before its first token pair is issued. */
|
|
26
|
+
static issue(user, familyId) {
|
|
27
|
+
return this.create({
|
|
28
|
+
family_id: familyId,
|
|
29
|
+
user_id: user.id,
|
|
30
|
+
user_type: user.userType,
|
|
31
|
+
revision: 0
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
static findByFamilyId(familyId) {
|
|
35
|
+
return this.first({ family_id: familyId });
|
|
36
|
+
}
|
|
37
|
+
/** Add a durable row for an existing lineage through a driver-native upsert. */
|
|
38
|
+
static async ensure(user, familyId) {
|
|
39
|
+
await this.atomic({ family_id: familyId }, { $setOnInsert: {
|
|
40
|
+
user_id: user.id,
|
|
41
|
+
user_type: user.userType,
|
|
42
|
+
revision: 0
|
|
43
|
+
} }, { upsert: true });
|
|
44
|
+
const family = await this.findByFamilyId(familyId);
|
|
45
|
+
if (!family) throw new Error("Token family upsert did not return a family");
|
|
46
|
+
if (family.get("user_id") !== user.id || family.get("user_type") !== user.userType) throw new Error("Token family belongs to a different user");
|
|
47
|
+
return family;
|
|
48
|
+
}
|
|
49
|
+
/** Every non-revoked family for a user, including a rotated refresh row. */
|
|
50
|
+
static activeFor(user) {
|
|
51
|
+
return this.query().where({
|
|
52
|
+
user_id: user.id,
|
|
53
|
+
user_type: user.userType,
|
|
54
|
+
revoked_at: null
|
|
55
|
+
}).get();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Compare-and-set the family revision. A zero result is a concurrent family
|
|
59
|
+
* transition, never a successful no-op.
|
|
60
|
+
*/
|
|
61
|
+
static advanceRevision(familyId, expectedRevision) {
|
|
62
|
+
return this.atomic({
|
|
63
|
+
family_id: familyId,
|
|
64
|
+
revision: expectedRevision,
|
|
65
|
+
revoked_at: null
|
|
66
|
+
}, { $inc: { revision: 1 } });
|
|
67
|
+
}
|
|
68
|
+
/** Stamp the durable family revoke marker after a successful CAS. */
|
|
69
|
+
static revoke(familyId, expectedRevision) {
|
|
70
|
+
return this.atomic({
|
|
71
|
+
family_id: familyId,
|
|
72
|
+
revision: expectedRevision + 1,
|
|
73
|
+
revoked_at: null
|
|
74
|
+
}, { $set: { revoked_at: /* @__PURE__ */ new Date() } });
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { AuthTokenFamily, authTokenFamilySchema };
|
|
80
|
+
//# sourceMappingURL=auth-token-family.model.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-token-family.model.mjs","names":[],"sources":["../../../../../../../../auth/src/models/auth-token-family/auth-token-family.model.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\nimport { v } from \"@warlock.js/seal\";\nimport type { Auth } from \"../auth.model\";\n\n/** Durable revision authority for one refresh-token rotation family. */\nexport const authTokenFamilySchema = v.object({\n family_id: v.string().required(),\n user_id: v.scalar().required(),\n user_type: v.string().required(),\n revision: v.number().default(0),\n revoked_at: v.date().optional(),\n});\n\n/** Coordinates family-wide refresh, replay containment, and logout transitions. */\nexport class AuthTokenFamily extends Model {\n public static table = \"auth_token_families\";\n\n public static schema = authTokenFamilySchema;\n\n /** Whether a family-wide revoke has made every member unusable. */\n public get isRevoked(): boolean {\n return Boolean(this.get(\"revoked_at\"));\n }\n\n /** Create the durable family record before its first token pair is issued. */\n public static issue(user: Auth, familyId: string) {\n return this.create({\n family_id: familyId,\n user_id: user.id,\n user_type: user.userType,\n revision: 0,\n });\n }\n\n public static findByFamilyId(familyId: string): Promise<AuthTokenFamily | null> {\n return this.first({ family_id: familyId });\n }\n\n /** Add a durable row for an existing lineage through a driver-native upsert. */\n public static async ensure(user: Auth, familyId: string): Promise<AuthTokenFamily> {\n await this.atomic(\n { family_id: familyId },\n { $setOnInsert: { user_id: user.id, user_type: user.userType, revision: 0 } },\n { upsert: true },\n );\n\n const family = await this.findByFamilyId(familyId);\n\n if (!family) throw new Error(\"Token family upsert did not return a family\");\n\n if (family.get(\"user_id\") !== user.id || family.get<string>(\"user_type\") !== user.userType) {\n throw new Error(\"Token family belongs to a different user\");\n }\n\n return family;\n }\n\n /** Every non-revoked family for a user, including a rotated refresh row. */\n public static activeFor(user: Auth): Promise<AuthTokenFamily[]> {\n return this.query()\n .where({ user_id: user.id, user_type: user.userType, revoked_at: null })\n .get();\n }\n\n /**\n * Compare-and-set the family revision. A zero result is a concurrent family\n * transition, never a successful no-op.\n */\n public static advanceRevision(familyId: string, expectedRevision: number): Promise<number> {\n return this.atomic(\n { family_id: familyId, revision: expectedRevision, revoked_at: null },\n { $inc: { revision: 1 } },\n );\n }\n\n /** Stamp the durable family revoke marker after a successful CAS. */\n public static revoke(familyId: string, expectedRevision: number): Promise<number> {\n return this.atomic(\n { family_id: familyId, revision: expectedRevision + 1, revoked_at: null },\n { $set: { revoked_at: new Date() } },\n );\n }\n}\n"],"mappings":";;;;;AAKA,MAAa,wBAAwB,EAAE,OAAO;CAC5C,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,UAAU,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC;CAC9B,YAAY,EAAE,KAAK,CAAC,CAAC,SAAS;AAChC,CAAC;;AAGD,IAAa,kBAAb,cAAqC,MAAM;;eACnB;;;gBAEC;;;CAGvB,IAAW,YAAqB;EAC9B,OAAO,QAAQ,KAAK,IAAI,YAAY,CAAC;CACvC;;CAGA,OAAc,MAAM,MAAY,UAAkB;EAChD,OAAO,KAAK,OAAO;GACjB,WAAW;GACX,SAAS,KAAK;GACd,WAAW,KAAK;GAChB,UAAU;EACZ,CAAC;CACH;CAEA,OAAc,eAAe,UAAmD;EAC9E,OAAO,KAAK,MAAM,EAAE,WAAW,SAAS,CAAC;CAC3C;;CAGA,aAAoB,OAAO,MAAY,UAA4C;EACjF,MAAM,KAAK,OACT,EAAE,WAAW,SAAS,GACtB,EAAE,cAAc;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;GAAU,UAAU;EAAE,EAAE,GAC5E,EAAE,QAAQ,KAAK,CACjB;EAEA,MAAM,SAAS,MAAM,KAAK,eAAe,QAAQ;EAEjD,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,6CAA6C;EAE1E,IAAI,OAAO,IAAI,SAAS,MAAM,KAAK,MAAM,OAAO,IAAY,WAAW,MAAM,KAAK,UAChF,MAAM,IAAI,MAAM,0CAA0C;EAG5D,OAAO;CACT;;CAGA,OAAc,UAAU,MAAwC;EAC9D,OAAO,KAAK,MAAM,CAAC,CAChB,MAAM;GAAE,SAAS,KAAK;GAAI,WAAW,KAAK;GAAU,YAAY;EAAK,CAAC,CAAC,CACvE,IAAI;CACT;;;;;CAMA,OAAc,gBAAgB,UAAkB,kBAA2C;EACzF,OAAO,KAAK,OACV;GAAE,WAAW;GAAU,UAAU;GAAkB,YAAY;EAAK,GACpE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAC1B;CACF;;CAGA,OAAc,OAAO,UAAkB,kBAA2C;EAChF,OAAO,KAAK,OACV;GAAE,WAAW;GAAU,UAAU,mBAAmB;GAAG,YAAY;EAAK,GACxE,EAAE,MAAM,EAAE,4BAAY,IAAI,KAAK,EAAE,EAAE,CACrC;CACF;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { AuthTokenFamily, authTokenFamilySchema } from "./auth-token-family.model.mjs";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AuthTokenFamily } from "./auth-token-family.model.mjs";
|
|
2
|
+
import { migrate } from "@warlock.js/cascade";
|
|
3
|
+
|
|
4
|
+
//#region ../auth/src/models/auth-token-family/migration.ts
|
|
5
|
+
/** Creates the durable authority shared by every token in a refresh family. */
|
|
6
|
+
const AuthTokenFamilyMigration = migrate(AuthTokenFamily, {
|
|
7
|
+
name: "authTokenFamily",
|
|
8
|
+
up() {
|
|
9
|
+
this.createTableIfNotExists();
|
|
10
|
+
this.primaryUuid();
|
|
11
|
+
this.string("family_id", 64).unique();
|
|
12
|
+
this.foreignId("user_id").index();
|
|
13
|
+
this.string("user_type", 50).nullable();
|
|
14
|
+
this.integer("revision").default(0);
|
|
15
|
+
this.timestamp("revoked_at").nullable();
|
|
16
|
+
this.timestamps();
|
|
17
|
+
},
|
|
18
|
+
down() {
|
|
19
|
+
this.dropTableIfExists();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { AuthTokenFamilyMigration };
|
|
25
|
+
//# sourceMappingURL=migration.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration.mjs","names":[],"sources":["../../../../../../../../auth/src/models/auth-token-family/migration.ts"],"sourcesContent":["import { migrate } from \"@warlock.js/cascade\";\nimport { AuthTokenFamily } from \"./auth-token-family.model\";\n\n/** Creates the durable authority shared by every token in a refresh family. */\nexport const AuthTokenFamilyMigration = migrate(AuthTokenFamily, {\n name: \"authTokenFamily\",\n up() {\n this.createTableIfNotExists();\n this.primaryUuid();\n this.string(\"family_id\", 64).unique();\n this.foreignId(\"user_id\").index();\n this.string(\"user_type\", 50).nullable();\n this.integer(\"revision\").default(0);\n this.timestamp(\"revoked_at\").nullable();\n this.timestamps();\n },\n down() {\n this.dropTableIfExists();\n },\n});\n"],"mappings":";;;;;AAIA,MAAa,2BAA2B,QAAQ,iBAAiB;CAC/D,MAAM;CACN,KAAK;EACH,KAAK,uBAAuB;EAC5B,KAAK,YAAY;EACjB,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,OAAO;EACpC,KAAK,UAAU,SAAS,CAAC,CAAC,MAAM;EAChC,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,SAAS;EACtC,KAAK,QAAQ,UAAU,CAAC,CAAC,QAAQ,CAAC;EAClC,KAAK,UAAU,YAAY,CAAC,CAAC,SAAS;EACtC,KAAK,WAAW;CAClB;CACA,OAAO;EACL,KAAK,kBAAkB;CACzB;AACF,CAAC"}
|
package/esm/models/index.d.mts
CHANGED
|
@@ -2,6 +2,7 @@ import { Auth } from "./auth.model.mjs";
|
|
|
2
2
|
import { AccessToken, accessTokenSchema } from "./access-token/access-token.model.mjs";
|
|
3
3
|
import { OneTimeToken, OneTimeTokenPurpose, oneTimeTokenSchema } from "./one-time-token/one-time-token.model.mjs";
|
|
4
4
|
import { RefreshToken, RefreshTokenIssueOptions, refreshTokenSchema } from "./refresh-token/refresh-token.model.mjs";
|
|
5
|
+
import { AuthTokenFamily, authTokenFamilySchema } from "./auth-token-family/auth-token-family.model.mjs";
|
|
5
6
|
import { PasskeyCredential, passkeyCredentialSchema } from "./passkey-credential/passkey-credential.model.mjs";
|
|
6
7
|
import { ProviderAccount, providerAccountSchema } from "./provider-account/provider-account.model.mjs";
|
|
7
8
|
//#region ../auth/src/models/index.d.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../../../auth/src/models/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../../../auth/src/models/index.ts"],"mappings":";;;;;;;;cAiBa,cAAA,gCAAc,oBAAA"}
|
package/esm/models/index.mjs
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { AccessToken, accessTokenSchema } from "./access-token/access-token.model.mjs";
|
|
2
2
|
import "./access-token/index.mjs";
|
|
3
|
+
import { AuthTokenFamily, authTokenFamilySchema } from "./auth-token-family/auth-token-family.model.mjs";
|
|
4
|
+
import "./auth-token-family/index.mjs";
|
|
3
5
|
import { OneTimeToken, oneTimeTokenSchema } from "./one-time-token/one-time-token.model.mjs";
|
|
4
6
|
import "./one-time-token/index.mjs";
|
|
5
7
|
import { RefreshToken, refreshTokenSchema } from "./refresh-token/refresh-token.model.mjs";
|
|
6
8
|
import "./refresh-token/index.mjs";
|
|
9
|
+
import { AccessTokenFamilyAssociationMigration } from "./access-token/family-association.migration.mjs";
|
|
7
10
|
import { AccessTokenMigration } from "./access-token/migration.mjs";
|
|
11
|
+
import { AuthTokenFamilyMigration } from "./auth-token-family/migration.mjs";
|
|
8
12
|
import { OneTimeTokenMigration } from "./one-time-token/migration.mjs";
|
|
9
13
|
import { PasskeyCredential, passkeyCredentialSchema } from "./passkey-credential/passkey-credential.model.mjs";
|
|
10
14
|
import { PasskeyCredentialMigration } from "./passkey-credential/migration.mjs";
|
|
11
15
|
import { ProviderAccount, providerAccountSchema } from "./provider-account/provider-account.model.mjs";
|
|
12
16
|
import { ProviderAccountMigration } from "./provider-account/migration.mjs";
|
|
13
17
|
import { RefreshTokenMigration } from "./refresh-token/migration.mjs";
|
|
18
|
+
import { RefreshTokenSuccessorAssociationMigration } from "./refresh-token/successor-association.migration.mjs";
|
|
14
19
|
import { Auth } from "./auth.model.mjs";
|
|
15
20
|
import "./passkey-credential/index.mjs";
|
|
16
21
|
import "./provider-account/index.mjs";
|
|
@@ -18,7 +23,10 @@ import "./provider-account/index.mjs";
|
|
|
18
23
|
//#region ../auth/src/models/index.ts
|
|
19
24
|
const authMigrations = [
|
|
20
25
|
AccessTokenMigration,
|
|
26
|
+
AccessTokenFamilyAssociationMigration,
|
|
21
27
|
RefreshTokenMigration,
|
|
28
|
+
RefreshTokenSuccessorAssociationMigration,
|
|
29
|
+
AuthTokenFamilyMigration,
|
|
22
30
|
OneTimeTokenMigration,
|
|
23
31
|
ProviderAccountMigration,
|
|
24
32
|
PasskeyCredentialMigration
|
package/esm/models/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../auth/src/models/index.ts"],"sourcesContent":["import { AccessTokenMigration } from \"./access-token/migration\";\nimport { OneTimeTokenMigration } from \"./one-time-token/migration\";\nimport { PasskeyCredentialMigration } from \"./passkey-credential/migration\";\nimport { ProviderAccountMigration } from \"./provider-account/migration\";\nimport { RefreshTokenMigration } from \"./refresh-token/migration\";\n\nexport * from \"./access-token\";\nexport * from \"./auth.model\";\nexport * from \"./one-time-token\";\nexport * from \"./passkey-credential\";\nexport * from \"./provider-account\";\nexport * from \"./refresh-token\";\n\nexport const authMigrations = [\n AccessTokenMigration,\n RefreshTokenMigration,\n OneTimeTokenMigration,\n ProviderAccountMigration,\n PasskeyCredentialMigration,\n];\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../auth/src/models/index.ts"],"sourcesContent":["import { AccessTokenFamilyAssociationMigration } from \"./access-token/family-association.migration\";\nimport { AccessTokenMigration } from \"./access-token/migration\";\nimport { AuthTokenFamilyMigration } from \"./auth-token-family/migration\";\nimport { OneTimeTokenMigration } from \"./one-time-token/migration\";\nimport { PasskeyCredentialMigration } from \"./passkey-credential/migration\";\nimport { ProviderAccountMigration } from \"./provider-account/migration\";\nimport { RefreshTokenMigration } from \"./refresh-token/migration\";\nimport { RefreshTokenSuccessorAssociationMigration } from \"./refresh-token/successor-association.migration\";\n\nexport * from \"./access-token\";\nexport * from \"./auth-token-family\";\nexport * from \"./auth.model\";\nexport * from \"./one-time-token\";\nexport * from \"./passkey-credential\";\nexport * from \"./provider-account\";\nexport * from \"./refresh-token\";\n\nexport const authMigrations = [\n AccessTokenMigration,\n AccessTokenFamilyAssociationMigration,\n RefreshTokenMigration,\n RefreshTokenSuccessorAssociationMigration,\n AuthTokenFamilyMigration,\n OneTimeTokenMigration,\n ProviderAccountMigration,\n PasskeyCredentialMigration,\n];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiBA,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
|
|
@@ -26,6 +26,12 @@ declare const refreshTokenSchema: import("@warlock.js/seal").ObjectValidator<{
|
|
|
26
26
|
};
|
|
27
27
|
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
28
28
|
isOptional: true;
|
|
29
|
+
}; /** The successor pair a bounded concurrent renewal may reuse. */
|
|
30
|
+
successor_access_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
31
|
+
isOptional: true;
|
|
32
|
+
};
|
|
33
|
+
successor_refresh_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
34
|
+
isOptional: true;
|
|
29
35
|
};
|
|
30
36
|
device_info: import("@warlock.js/seal").RecordValidator & {
|
|
31
37
|
valueValidator: import("@warlock.js/seal").AnyValidator & import("@warlock.js/seal").StandardSchemaV1<any, any>;
|
|
@@ -43,6 +49,12 @@ declare const refreshTokenSchema: import("@warlock.js/seal").ObjectValidator<{
|
|
|
43
49
|
};
|
|
44
50
|
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
45
51
|
isOptional: true;
|
|
52
|
+
}; /** The successor pair a bounded concurrent renewal may reuse. */
|
|
53
|
+
successor_access_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
54
|
+
isOptional: true;
|
|
55
|
+
};
|
|
56
|
+
successor_refresh_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
57
|
+
isOptional: true;
|
|
46
58
|
};
|
|
47
59
|
device_info: import("@warlock.js/seal").RecordValidator & {
|
|
48
60
|
valueValidator: import("@warlock.js/seal").AnyValidator & import("@warlock.js/seal").StandardSchemaV1<any, any>;
|
|
@@ -60,6 +72,12 @@ declare const refreshTokenSchema: import("@warlock.js/seal").ObjectValidator<{
|
|
|
60
72
|
};
|
|
61
73
|
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
62
74
|
isOptional: true;
|
|
75
|
+
}; /** The successor pair a bounded concurrent renewal may reuse. */
|
|
76
|
+
successor_access_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
77
|
+
isOptional: true;
|
|
78
|
+
};
|
|
79
|
+
successor_refresh_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
80
|
+
isOptional: true;
|
|
63
81
|
};
|
|
64
82
|
device_info: import("@warlock.js/seal").RecordValidator & {
|
|
65
83
|
valueValidator: import("@warlock.js/seal").AnyValidator & import("@warlock.js/seal").StandardSchemaV1<any, any>;
|
|
@@ -98,6 +116,12 @@ declare class RefreshToken extends Model {
|
|
|
98
116
|
};
|
|
99
117
|
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
100
118
|
isOptional: true;
|
|
119
|
+
}; /** The successor pair a bounded concurrent renewal may reuse. */
|
|
120
|
+
successor_access_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
121
|
+
isOptional: true;
|
|
122
|
+
};
|
|
123
|
+
successor_refresh_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
124
|
+
isOptional: true;
|
|
101
125
|
};
|
|
102
126
|
device_info: import("@warlock.js/seal").RecordValidator & {
|
|
103
127
|
valueValidator: import("@warlock.js/seal").AnyValidator & import("@warlock.js/seal").StandardSchemaV1<any, any>;
|
|
@@ -115,6 +139,12 @@ declare class RefreshToken extends Model {
|
|
|
115
139
|
};
|
|
116
140
|
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
117
141
|
isOptional: true;
|
|
142
|
+
}; /** The successor pair a bounded concurrent renewal may reuse. */
|
|
143
|
+
successor_access_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
144
|
+
isOptional: true;
|
|
145
|
+
};
|
|
146
|
+
successor_refresh_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
147
|
+
isOptional: true;
|
|
118
148
|
};
|
|
119
149
|
device_info: import("@warlock.js/seal").RecordValidator & {
|
|
120
150
|
valueValidator: import("@warlock.js/seal").AnyValidator & import("@warlock.js/seal").StandardSchemaV1<any, any>;
|
|
@@ -132,6 +162,12 @@ declare class RefreshToken extends Model {
|
|
|
132
162
|
};
|
|
133
163
|
revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
|
|
134
164
|
isOptional: true;
|
|
165
|
+
}; /** The successor pair a bounded concurrent renewal may reuse. */
|
|
166
|
+
successor_access_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
167
|
+
isOptional: true;
|
|
168
|
+
};
|
|
169
|
+
successor_refresh_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
170
|
+
isOptional: true;
|
|
135
171
|
};
|
|
136
172
|
device_info: import("@warlock.js/seal").RecordValidator & {
|
|
137
173
|
valueValidator: import("@warlock.js/seal").AnyValidator & import("@warlock.js/seal").StandardSchemaV1<any, any>;
|
|
@@ -204,6 +240,13 @@ declare class RefreshToken extends Model {
|
|
|
204
240
|
* revoke a token that actually belongs to them.
|
|
205
241
|
*/
|
|
206
242
|
static findForUser(user: Auth, token: string): Promise<RefreshToken | null>;
|
|
243
|
+
/** Find any member of a family, including an already-revoked legacy member. */
|
|
244
|
+
static findInFamily(familyId: string): Promise<RefreshToken | null>;
|
|
245
|
+
/**
|
|
246
|
+
* Every refresh lineage for a user, including rotated rows. Logout uses this
|
|
247
|
+
* to bootstrap durable family records before it starts family revocations.
|
|
248
|
+
*/
|
|
249
|
+
static familiesForUser(user: Auth): Promise<RefreshToken[]>;
|
|
207
250
|
/**
|
|
208
251
|
* Delete a specific refresh token belonging to the user.
|
|
209
252
|
*/
|
|
@@ -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
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiBnB,wBAAA;EACV,QAAA;EACA,SAAA;EACA,UAAA,GAAa,UAAU;AAAA;;;;;;;;;;;cAaZ,YAAA,SAAqB,KAAA;EAAA,OAClB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAhBG;;;;;;EAAA,OAwBT,MAAA;EAXH;;;;;;EAAA,OAmBG,cAAA;;MAGH,QAAA;;;;;;;;;;;MAcA,SAAA;;;;;MAYA,YAAA;;MAKA,SAAA;EAYY;EAAA,IAPZ,OAAA;EAgCgB;;;EAzBd,MAAA,IAAU,OAAA;EAgCyD;;;;;;;EArBnE,cAAA,IAAkB,OAAA;EA8DK;;;EAhDvB,UAAA,IAAc,OAAA;EAuD0B;;;EAAA,OAhDvC,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,OAAA,EAAS,wBAAA,GAAwB,OAAA,CAAA,YAAA;EAuEzC;;;EAAA,OAnDzB,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,YAAA;EAmEE;;;;EAAA,OA3DtC,WAAA,CAAY,IAAA,EAAM,IAAA,EAAM,KAAA,WAAgB,OAAA,CAAQ,YAAA;EA8Gb;EAAA,OAzGnC,YAAA,CAAa,QAAA,WAAmB,OAAA,CAAQ,YAAA;EAwHJ;;;;EAAA,OAhHpC,eAAA,CAAgB,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;EA1InB;;;EAAA,OAiJlB,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA,WAAa,OAAA;;;;SAOvC,SAAA,CAAU,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;;;;;;;;;SAgBzB,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;;;;;SAgBlC,YAAA,CAAa,QAAA,WAAmB,OAAA,CAAQ,YAAA;;;;;;SAexC,UAAA,CAAW,IAAA,EAAM,IAAA,EAAM,GAAA,WAAc,OAAA;;;;;;SAoBrC,YAAA,IAAgB,OAAA,CAAQ,YAAA;;;;;;;SAgBxB,iBAAA,IAAqB,OAAA,CAAQ,YAAA;;;;;;;;;;SAe7B,kBAAA,IAAsB,OAAA,CAAQ,YAAA;AAAA"}
|