@hilbras/keystone 1.9.0 → 2.5.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 +386 -0
- package/README.md +150 -32
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/db/migrations/0015_chief_vargas.sql +7 -0
- package/dist/db/migrations/0016_gorgeous_charles_xavier.sql +5 -0
- package/dist/db/migrations/0017_flashy_nekra.sql +1 -0
- package/dist/db/migrations/meta/0015_snapshot.json +4340 -0
- package/dist/db/migrations/meta/0016_snapshot.json +4363 -0
- package/dist/db/migrations/meta/0017_snapshot.json +4370 -0
- package/dist/db/migrations/meta/_journal.json +21 -0
- package/dist/db/schema.d.ts +138 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +39 -1
- package/dist/db/schema.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/headerSanitization.d.ts +15 -0
- package/dist/plugins/headerSanitization.d.ts.map +1 -0
- package/dist/plugins/headerSanitization.js +39 -0
- package/dist/plugins/headerSanitization.js.map +1 -0
- package/dist/plugins/mtls.d.ts +21 -2
- package/dist/plugins/mtls.d.ts.map +1 -1
- package/dist/plugins/mtls.js +94 -47
- package/dist/plugins/mtls.js.map +1 -1
- package/dist/plugins/rateLimit.d.ts +5 -4
- package/dist/plugins/rateLimit.d.ts.map +1 -1
- package/dist/plugins/rateLimit.js +11 -8
- package/dist/plugins/rateLimit.js.map +1 -1
- package/dist/repositories/application.d.ts +1 -1
- package/dist/repositories/application.d.ts.map +1 -1
- package/dist/repositories/application.js +8 -2
- package/dist/repositories/application.js.map +1 -1
- package/dist/repositories/session.d.ts +1 -0
- package/dist/repositories/session.d.ts.map +1 -1
- package/dist/repositories/types.d.ts +5 -1
- package/dist/repositories/types.d.ts.map +1 -1
- package/dist/routes/admin/organizations.d.ts.map +1 -1
- package/dist/routes/admin/organizations.js +31 -2
- package/dist/routes/admin/organizations.js.map +1 -1
- package/dist/routes/oauth.d.ts.map +1 -1
- package/dist/routes/oauth.js +38 -2
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/oauth2.d.ts.map +1 -1
- package/dist/routes/oauth2.js +84 -11
- package/dist/routes/oauth2.js.map +1 -1
- package/dist/routes/saml.d.ts +24 -0
- package/dist/routes/saml.d.ts.map +1 -1
- package/dist/routes/saml.js +32 -3
- package/dist/routes/saml.js.map +1 -1
- package/dist/routes/serviceAccounts.d.ts.map +1 -1
- package/dist/routes/serviceAccounts.js +61 -1
- package/dist/routes/serviceAccounts.js.map +1 -1
- package/dist/routes/totp.d.ts.map +1 -1
- package/dist/routes/totp.js +4 -5
- package/dist/routes/totp.js.map +1 -1
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/types.d.ts +4 -1
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/services/application/organization.d.ts +5 -1
- package/dist/services/application/organization.d.ts.map +1 -1
- package/dist/services/application/organization.js.map +1 -1
- package/dist/services/connectors/google.d.ts +18 -1
- package/dist/services/connectors/google.d.ts.map +1 -1
- package/dist/services/connectors/google.js +25 -3
- package/dist/services/connectors/google.js.map +1 -1
- package/dist/services/connectors/oidc.d.ts +14 -2
- package/dist/services/connectors/oidc.d.ts.map +1 -1
- package/dist/services/connectors/oidc.js +29 -3
- package/dist/services/connectors/oidc.js.map +1 -1
- package/dist/services/connectors/types.d.ts +13 -1
- package/dist/services/connectors/types.d.ts.map +1 -1
- package/dist/services/domain/authentication.d.ts.map +1 -1
- package/dist/services/domain/authentication.js +36 -19
- package/dist/services/domain/authentication.js.map +1 -1
- package/dist/services/domain/organization.d.ts +5 -1
- package/dist/services/domain/organization.d.ts.map +1 -1
- package/dist/services/domain/organization.js.map +1 -1
- package/dist/services/events/types.d.ts +1 -1
- package/dist/services/events/types.d.ts.map +1 -1
- package/dist/services/events/validate.d.ts.map +1 -1
- package/dist/services/events/validate.js +6 -0
- package/dist/services/events/validate.js.map +1 -1
- package/dist/services/magicLinks.d.ts.map +1 -1
- package/dist/services/magicLinks.js +13 -9
- package/dist/services/magicLinks.js.map +1 -1
- package/dist/services/oauth2.d.ts +72 -2
- package/dist/services/oauth2.d.ts.map +1 -1
- package/dist/services/oauth2.js +106 -6
- package/dist/services/oauth2.js.map +1 -1
- package/dist/services/redirectUri.d.ts +56 -0
- package/dist/services/redirectUri.d.ts.map +1 -0
- package/dist/services/redirectUri.js +129 -0
- package/dist/services/redirectUri.js.map +1 -0
- package/dist/services/serviceAccounts.d.ts +20 -0
- package/dist/services/serviceAccounts.d.ts.map +1 -1
- package/dist/services/serviceAccounts.js +47 -1
- package/dist/services/serviceAccounts.js.map +1 -1
- package/dist/services/sessionRevocation.d.ts +76 -0
- package/dist/services/sessionRevocation.d.ts.map +1 -0
- package/dist/services/sessionRevocation.js +86 -0
- package/dist/services/sessionRevocation.js.map +1 -0
- package/dist/services/singleUse.d.ts +68 -0
- package/dist/services/singleUse.d.ts.map +1 -0
- package/dist/services/singleUse.js +54 -0
- package/dist/services/singleUse.js.map +1 -0
- package/dist/services/smsOtp.d.ts.map +1 -1
- package/dist/services/smsOtp.js +12 -8
- package/dist/services/smsOtp.js.map +1 -1
- package/dist/services/tokens.d.ts +2 -0
- package/dist/services/tokens.d.ts.map +1 -1
- package/dist/services/tokens.js +5 -0
- package/dist/services/tokens.js.map +1 -1
- package/dist/services/trustedProxies.d.ts +90 -0
- package/dist/services/trustedProxies.d.ts.map +1 -0
- package/dist/services/trustedProxies.js +283 -0
- package/dist/services/trustedProxies.js.map +1 -0
- package/dist/setup-server.js +2 -1
- package/dist/setup-server.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth2.js","sourceRoot":"","sources":["../../src/services/oauth2.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"oauth2.js","sourceRoot":"","sources":["../../src/services/oauth2.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redirect URI validation.
|
|
3
|
+
*
|
|
4
|
+
* Redirect URIs are the one place an authorization server sends a user's browser
|
|
5
|
+
* somewhere on its own authority. Anything accepted here becomes a `Location`
|
|
6
|
+
* header the identity provider itself emits, so the accepted set has to be a
|
|
7
|
+
* set a browser cannot be talked out of.
|
|
8
|
+
*
|
|
9
|
+
* Two distinct jobs, kept separate on purpose:
|
|
10
|
+
*
|
|
11
|
+
* - {@link validateRedirectUri} decides whether a URI may be **registered**.
|
|
12
|
+
* - {@link isRedirectUriRegistered} decides whether a URI may be **used**.
|
|
13
|
+
*
|
|
14
|
+
* Matching is exact string comparison. That is the strictest form available and
|
|
15
|
+
* the one RFC 9700 requires: no prefix matching, no normalization, no wildcards.
|
|
16
|
+
* Normalizing before comparing is what turns a strict check into a loose one,
|
|
17
|
+
* because the two sides then stop being compared as the bytes that will
|
|
18
|
+
* actually be sent.
|
|
19
|
+
*/
|
|
20
|
+
export interface RedirectUriRejection {
|
|
21
|
+
ok: false;
|
|
22
|
+
reason: string;
|
|
23
|
+
}
|
|
24
|
+
export type RedirectUriCheck = {
|
|
25
|
+
ok: true;
|
|
26
|
+
} | RedirectUriRejection;
|
|
27
|
+
/**
|
|
28
|
+
* Whether a redirect URI may be registered.
|
|
29
|
+
*
|
|
30
|
+
* Rejects, in order of how badly they fail:
|
|
31
|
+
* - `javascript:`, `data:`, and friends — script execution on our own origin
|
|
32
|
+
* - wildcards — accepted by the URL parser but can never match exactly, so they
|
|
33
|
+
* mislead the operator into thinking a redirect is configured when it is not
|
|
34
|
+
* - fragments — never transmitted to the server, so an exact comparison against
|
|
35
|
+
* them can never succeed
|
|
36
|
+
* - embedded credentials — `https://user:pass@host` is a phishing primitive
|
|
37
|
+
* - plaintext HTTP to anything that is not loopback
|
|
38
|
+
*/
|
|
39
|
+
export declare function validateRedirectUri(uri: string): RedirectUriCheck;
|
|
40
|
+
/**
|
|
41
|
+
* Validate a whole registration set, returning every problem rather than the
|
|
42
|
+
* first, so an operator fixing a configuration sees all of it at once.
|
|
43
|
+
*/
|
|
44
|
+
export declare function validateRedirectUris(uris: string[]): RedirectUriRejection[];
|
|
45
|
+
/**
|
|
46
|
+
* Whether a requested redirect URI is registered.
|
|
47
|
+
*
|
|
48
|
+
* Exact string comparison, deliberately. The requested value is compared as the
|
|
49
|
+
* exact bytes that will be sent in the `Location` header, with no parsing,
|
|
50
|
+
* case-folding, trailing-slash adjustment, or default-port removal. Any of those
|
|
51
|
+
* would widen the check: `https://example.com` and `https://example.com/` are
|
|
52
|
+
* different origins paths-wise, and a comparison that treats them as equal is a
|
|
53
|
+
* comparison that can be made to match something it should not.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isRedirectUriRegistered(registered: string[], requested: string | undefined): boolean;
|
|
56
|
+
//# sourceMappingURL=redirectUri.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirectUri.d.ts","sourceRoot":"","sources":["../../src/services/redirectUri.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,gBAAgB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,oBAAoB,CAAC;AAcnE;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CA6DjE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,oBAAoB,EAAE,CAiB3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGpG"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redirect URI validation.
|
|
3
|
+
*
|
|
4
|
+
* Redirect URIs are the one place an authorization server sends a user's browser
|
|
5
|
+
* somewhere on its own authority. Anything accepted here becomes a `Location`
|
|
6
|
+
* header the identity provider itself emits, so the accepted set has to be a
|
|
7
|
+
* set a browser cannot be talked out of.
|
|
8
|
+
*
|
|
9
|
+
* Two distinct jobs, kept separate on purpose:
|
|
10
|
+
*
|
|
11
|
+
* - {@link validateRedirectUri} decides whether a URI may be **registered**.
|
|
12
|
+
* - {@link isRedirectUriRegistered} decides whether a URI may be **used**.
|
|
13
|
+
*
|
|
14
|
+
* Matching is exact string comparison. That is the strictest form available and
|
|
15
|
+
* the one RFC 9700 requires: no prefix matching, no normalization, no wildcards.
|
|
16
|
+
* Normalizing before comparing is what turns a strict check into a loose one,
|
|
17
|
+
* because the two sides then stop being compared as the bytes that will
|
|
18
|
+
* actually be sent.
|
|
19
|
+
*/
|
|
20
|
+
/** Schemes that would execute script or inline a document if navigated to. */
|
|
21
|
+
const SCRIPT_SCHEMES = new Set(["javascript", "data", "vbscript", "blob", "file"]);
|
|
22
|
+
/**
|
|
23
|
+
* Hostnames that may legitimately be reached over plaintext HTTP, because they
|
|
24
|
+
* never leave the developer's machine.
|
|
25
|
+
*/
|
|
26
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "::1", "0.0.0.0"]);
|
|
27
|
+
/** `scheme:` per RFC 3986, which custom app schemes (`myapp://`) must match. */
|
|
28
|
+
const SCHEME = /^([a-z][a-z0-9+.-]*):/i;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a redirect URI may be registered.
|
|
31
|
+
*
|
|
32
|
+
* Rejects, in order of how badly they fail:
|
|
33
|
+
* - `javascript:`, `data:`, and friends — script execution on our own origin
|
|
34
|
+
* - wildcards — accepted by the URL parser but can never match exactly, so they
|
|
35
|
+
* mislead the operator into thinking a redirect is configured when it is not
|
|
36
|
+
* - fragments — never transmitted to the server, so an exact comparison against
|
|
37
|
+
* them can never succeed
|
|
38
|
+
* - embedded credentials — `https://user:pass@host` is a phishing primitive
|
|
39
|
+
* - plaintext HTTP to anything that is not loopback
|
|
40
|
+
*/
|
|
41
|
+
export function validateRedirectUri(uri) {
|
|
42
|
+
if (typeof uri !== "string" || uri.trim() === "") {
|
|
43
|
+
return { ok: false, reason: "must be a non-empty string" };
|
|
44
|
+
}
|
|
45
|
+
const value = uri.trim();
|
|
46
|
+
// A wildcard is a valid URL character sequence to the parser, so it has to be
|
|
47
|
+
// rejected explicitly rather than caught by scheme or host rules.
|
|
48
|
+
if (value.includes("*")) {
|
|
49
|
+
return { ok: false, reason: "must not contain a wildcard; register each redirect URI exactly" };
|
|
50
|
+
}
|
|
51
|
+
let url;
|
|
52
|
+
try {
|
|
53
|
+
url = new URL(value);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return { ok: false, reason: "must be a valid absolute URL" };
|
|
57
|
+
}
|
|
58
|
+
const scheme = url.protocol.replace(/:$/, "").toLowerCase();
|
|
59
|
+
if (SCRIPT_SCHEMES.has(scheme)) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
reason: `scheme "${scheme}" would execute script or inline a document and is never a valid redirect target`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (url.hash) {
|
|
66
|
+
return { ok: false, reason: "must not contain a fragment; fragments are not sent to the server" };
|
|
67
|
+
}
|
|
68
|
+
if (url.username || url.password) {
|
|
69
|
+
return { ok: false, reason: "must not embed credentials" };
|
|
70
|
+
}
|
|
71
|
+
if (scheme === "https") {
|
|
72
|
+
return { ok: true };
|
|
73
|
+
}
|
|
74
|
+
if (scheme === "http") {
|
|
75
|
+
const host = url.hostname.toLowerCase();
|
|
76
|
+
if (LOOPBACK_HOSTS.has(host))
|
|
77
|
+
return { ok: true };
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
reason: "must use https; plaintext http is accepted only for loopback addresses during development",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// A custom scheme is how native and mobile clients receive the redirect, so it
|
|
84
|
+
// is allowed — provided it is not one of the script-bearing schemes handled
|
|
85
|
+
// above. It must be a reverse-DNS style identifier, not a bare word.
|
|
86
|
+
if (SCHEME.test(value) && url.protocol !== ":") {
|
|
87
|
+
if (!/^[a-z][a-z0-9+.-]*:$/i.test(url.protocol)) {
|
|
88
|
+
return { ok: false, reason: "scheme is malformed" };
|
|
89
|
+
}
|
|
90
|
+
return { ok: true };
|
|
91
|
+
}
|
|
92
|
+
return { ok: false, reason: `scheme "${scheme}" is not supported` };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Validate a whole registration set, returning every problem rather than the
|
|
96
|
+
* first, so an operator fixing a configuration sees all of it at once.
|
|
97
|
+
*/
|
|
98
|
+
export function validateRedirectUris(uris) {
|
|
99
|
+
const problems = [];
|
|
100
|
+
for (const uri of uris) {
|
|
101
|
+
const result = validateRedirectUri(uri);
|
|
102
|
+
if (!result.ok)
|
|
103
|
+
problems.push({ ok: false, reason: `${uri}: ${result.reason}` });
|
|
104
|
+
}
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
for (const uri of uris) {
|
|
107
|
+
if (seen.has(uri)) {
|
|
108
|
+
problems.push({ ok: false, reason: `${uri}: registered more than once` });
|
|
109
|
+
}
|
|
110
|
+
seen.add(uri);
|
|
111
|
+
}
|
|
112
|
+
return problems;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Whether a requested redirect URI is registered.
|
|
116
|
+
*
|
|
117
|
+
* Exact string comparison, deliberately. The requested value is compared as the
|
|
118
|
+
* exact bytes that will be sent in the `Location` header, with no parsing,
|
|
119
|
+
* case-folding, trailing-slash adjustment, or default-port removal. Any of those
|
|
120
|
+
* would widen the check: `https://example.com` and `https://example.com/` are
|
|
121
|
+
* different origins paths-wise, and a comparison that treats them as equal is a
|
|
122
|
+
* comparison that can be made to match something it should not.
|
|
123
|
+
*/
|
|
124
|
+
export function isRedirectUriRegistered(registered, requested) {
|
|
125
|
+
if (typeof requested !== "string" || requested === "")
|
|
126
|
+
return false;
|
|
127
|
+
return registered.includes(requested);
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=redirectUri.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirectUri.js","sourceRoot":"","sources":["../../src/services/redirectUri.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ export declare function createServiceAccount(input: {
|
|
|
3
3
|
orgId: string;
|
|
4
4
|
name: string;
|
|
5
5
|
description?: string;
|
|
6
|
+
certFingerprint?: string | null;
|
|
6
7
|
}): Promise<ServiceAccount>;
|
|
7
8
|
export declare function findServiceAccountsByOrgId(orgId: string): Promise<ServiceAccount[]>;
|
|
8
9
|
export declare function findServiceAccountById(id: string, orgId: string): Promise<ServiceAccount | undefined>;
|
|
@@ -11,6 +12,25 @@ export declare function updateServiceAccount(id: string, orgId: string, updates:
|
|
|
11
12
|
description: string;
|
|
12
13
|
isActive: boolean;
|
|
13
14
|
}>): Promise<ServiceAccount | undefined>;
|
|
15
|
+
/**
|
|
16
|
+
* Bind or clear the client certificate this account authenticates with.
|
|
17
|
+
*
|
|
18
|
+
* Clearing sets the column to null, which removes certificate authentication
|
|
19
|
+
* for the account entirely — an unbound account can only use an API key. The
|
|
20
|
+
* unique index on `cert_fingerprint` is what stops one certificate being bound
|
|
21
|
+
* to two accounts; a conflict surfaces as an error here rather than silently
|
|
22
|
+
* picking a winner.
|
|
23
|
+
*
|
|
24
|
+
* @throws if `fingerprint` is not a well-formed SHA-256 digest, or if the
|
|
25
|
+
* certificate is already bound to a different account.
|
|
26
|
+
*/
|
|
27
|
+
export declare function setServiceAccountCertificate(id: string, orgId: string, fingerprint: string | null): Promise<ServiceAccount | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Permanently stop an account authenticating. Kept separate from
|
|
30
|
+
* `isActive: false` so a later reactivation cannot silently restore access —
|
|
31
|
+
* reactivation requires clearing `revokedAt` deliberately.
|
|
32
|
+
*/
|
|
33
|
+
export declare function revokeServiceAccount(id: string, orgId: string): Promise<ServiceAccount | undefined>;
|
|
14
34
|
export declare function listServiceAccountApiKeys(serviceAccountId: string): Promise<{
|
|
15
35
|
id: string;
|
|
16
36
|
name: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serviceAccounts.d.ts","sourceRoot":"","sources":["../../src/services/serviceAccounts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"serviceAccounts.d.ts","sourceRoot":"","sources":["../../src/services/serviceAccounts.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEhF,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC,GAAG,OAAO,CAAC,cAAc,CAAC,CAW1B;AAED,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAMzF;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAarC;AAED,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,GACzE,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAOrC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,4BAA4B,CAChD,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAmBrC;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAOrC;AAED,wBAAsB,yBAAyB,CAAC,gBAAgB,EAAE,MAAM;;;;;;;;;KAcvE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { eq, and, sql } from "drizzle-orm";
|
|
1
|
+
import { eq, and, isNull, sql } from "drizzle-orm";
|
|
2
|
+
import { canonicalFingerprint, isValidFingerprint } from "./trustedProxies.js";
|
|
2
3
|
import { db } from "../db/index.js";
|
|
3
4
|
import { serviceAccounts, apiKeys } from "../db/schema.js";
|
|
4
5
|
export async function createServiceAccount(input) {
|
|
@@ -8,6 +9,7 @@ export async function createServiceAccount(input) {
|
|
|
8
9
|
orgId: input.orgId,
|
|
9
10
|
name: input.name,
|
|
10
11
|
description: input.description ?? null,
|
|
12
|
+
certFingerprint: input.certFingerprint ?? null,
|
|
11
13
|
})
|
|
12
14
|
.returning();
|
|
13
15
|
return record;
|
|
@@ -35,6 +37,50 @@ export async function updateServiceAccount(id, orgId, updates) {
|
|
|
35
37
|
.returning();
|
|
36
38
|
return updated;
|
|
37
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Bind or clear the client certificate this account authenticates with.
|
|
42
|
+
*
|
|
43
|
+
* Clearing sets the column to null, which removes certificate authentication
|
|
44
|
+
* for the account entirely — an unbound account can only use an API key. The
|
|
45
|
+
* unique index on `cert_fingerprint` is what stops one certificate being bound
|
|
46
|
+
* to two accounts; a conflict surfaces as an error here rather than silently
|
|
47
|
+
* picking a winner.
|
|
48
|
+
*
|
|
49
|
+
* @throws if `fingerprint` is not a well-formed SHA-256 digest, or if the
|
|
50
|
+
* certificate is already bound to a different account.
|
|
51
|
+
*/
|
|
52
|
+
export async function setServiceAccountCertificate(id, orgId, fingerprint) {
|
|
53
|
+
// Canonicalize here rather than trusting the caller. The hex and
|
|
54
|
+
// colon-separated spellings of one certificate must not become two bindings,
|
|
55
|
+
// and this is the only place every caller is guaranteed to pass through.
|
|
56
|
+
let canonical = null;
|
|
57
|
+
if (fingerprint !== null) {
|
|
58
|
+
const trimmed = fingerprint.trim();
|
|
59
|
+
if (!isValidFingerprint(trimmed)) {
|
|
60
|
+
throw new Error("fingerprint must be a SHA-256 digest (64 hex characters)");
|
|
61
|
+
}
|
|
62
|
+
canonical = canonicalFingerprint(trimmed);
|
|
63
|
+
}
|
|
64
|
+
const [updated] = await db
|
|
65
|
+
.update(serviceAccounts)
|
|
66
|
+
.set({ certFingerprint: canonical, updatedAt: sql `now()` })
|
|
67
|
+
.where(and(eq(serviceAccounts.id, id), eq(serviceAccounts.orgId, orgId)))
|
|
68
|
+
.returning();
|
|
69
|
+
return updated;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Permanently stop an account authenticating. Kept separate from
|
|
73
|
+
* `isActive: false` so a later reactivation cannot silently restore access —
|
|
74
|
+
* reactivation requires clearing `revokedAt` deliberately.
|
|
75
|
+
*/
|
|
76
|
+
export async function revokeServiceAccount(id, orgId) {
|
|
77
|
+
const [updated] = await db
|
|
78
|
+
.update(serviceAccounts)
|
|
79
|
+
.set({ isActive: false, revokedAt: sql `now()`, updatedAt: sql `now()` })
|
|
80
|
+
.where(and(eq(serviceAccounts.id, id), eq(serviceAccounts.orgId, orgId), isNull(serviceAccounts.revokedAt)))
|
|
81
|
+
.returning();
|
|
82
|
+
return updated;
|
|
83
|
+
}
|
|
38
84
|
export async function listServiceAccountApiKeys(serviceAccountId) {
|
|
39
85
|
return db
|
|
40
86
|
.select({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serviceAccounts.js","sourceRoot":"","sources":["../../src/services/serviceAccounts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"serviceAccounts.js","sourceRoot":"","sources":["../../src/services/serviceAccounts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized credential revocation.
|
|
3
|
+
*
|
|
4
|
+
* Every "this account should stop being logged in" operation routes through
|
|
5
|
+
* here. Before this existed, revocation was open-coded per call site, and the
|
|
6
|
+
* most important call site was missing entirely: completing a password reset
|
|
7
|
+
* changed the password and left every existing session and refresh token
|
|
8
|
+
* working. That defeats the purpose of a reset, which is the standard response
|
|
9
|
+
* to a suspected compromise — the intruder keeps their session.
|
|
10
|
+
*
|
|
11
|
+
* The primitives below are deliberately separate so a caller can be precise
|
|
12
|
+
* about what it invalidates, and so the composite operations are visibly
|
|
13
|
+
* composed rather than reimplemented at each site.
|
|
14
|
+
*/
|
|
15
|
+
export interface RevocationCounts {
|
|
16
|
+
refreshTokens: number;
|
|
17
|
+
sessions: number;
|
|
18
|
+
recoveryCredentials: number;
|
|
19
|
+
}
|
|
20
|
+
export interface RevokeOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Keep this session and its refresh token alive, for a change the user is
|
|
23
|
+
* making to their own account.
|
|
24
|
+
*
|
|
25
|
+
* Never pass it on a reset the user did not initiate themselves: the current
|
|
26
|
+
* session is precisely what an attacker would be holding, and preserving it
|
|
27
|
+
* would leave them in.
|
|
28
|
+
*/
|
|
29
|
+
exceptSessionId?: string;
|
|
30
|
+
/** Keep this refresh token alive, for the same reason. */
|
|
31
|
+
exceptRefreshTokenId?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Revoke the user's refresh tokens, so no further access token can be minted
|
|
35
|
+
* from an existing session.
|
|
36
|
+
*/
|
|
37
|
+
export declare function revokeRefreshTokens(userId: string, options?: RevokeOptions): Promise<number>;
|
|
38
|
+
/**
|
|
39
|
+
* Revoke the user's authentication sessions.
|
|
40
|
+
*
|
|
41
|
+
* A revoked session stops authorising requests immediately; it is not merely
|
|
42
|
+
* hidden from a session list.
|
|
43
|
+
*/
|
|
44
|
+
export declare function revokeAuthenticationSessions(userId: string, options?: RevokeOptions): Promise<number>;
|
|
45
|
+
/**
|
|
46
|
+
* Revoke outstanding recovery credentials.
|
|
47
|
+
*
|
|
48
|
+
* A reset token already sitting in someone's inbox stays valid until it
|
|
49
|
+
* expires unless it is spent here. Someone who intercepted an earlier reset
|
|
50
|
+
* email could otherwise complete it after the user had already recovered.
|
|
51
|
+
*/
|
|
52
|
+
export declare function revokeRecoveryCredentials(userId: string): Promise<number>;
|
|
53
|
+
/**
|
|
54
|
+
* Log the user out everywhere: sessions and refresh tokens together.
|
|
55
|
+
*
|
|
56
|
+
* This is what most credential changes want. Revoking only one leaves a live
|
|
57
|
+
* credential behind: a session with no refresh token still authorises requests
|
|
58
|
+
* until it expires, and a refresh token with no session can still mint access
|
|
59
|
+
* tokens.
|
|
60
|
+
*/
|
|
61
|
+
export declare function revokeUserSessions(userId: string, options?: RevokeOptions): Promise<RevocationCounts>;
|
|
62
|
+
/**
|
|
63
|
+
* Everything: sessions, refresh tokens, and outstanding recovery credentials.
|
|
64
|
+
*
|
|
65
|
+
* Used after a password reset.
|
|
66
|
+
*
|
|
67
|
+
* API keys are deliberately **not** revoked. They are separately issued,
|
|
68
|
+
* long-lived credentials belonging to integrations rather than to the person,
|
|
69
|
+
* and silently killing them on a password reset breaks deployments without
|
|
70
|
+
* improving the account's own security. The residual gap is real — an API key
|
|
71
|
+
* minted by an attacker who already held the password survives — and the right
|
|
72
|
+
* answer is key expiry and rotation rather than coupling key lifetime to a
|
|
73
|
+
* human's password.
|
|
74
|
+
*/
|
|
75
|
+
export declare function revokeAllUserCredentials(userId: string, options?: RevokeOptions): Promise<RevocationCounts>;
|
|
76
|
+
//# sourceMappingURL=sessionRevocation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionRevocation.d.ts","sourceRoot":"","sources":["../../src/services/sessionRevocation.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAID;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO/E;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,gBAAgB,CAAC,CAI3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,gBAAgB,CAAC,CAK3B"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { and, eq, isNull, ne } from "drizzle-orm";
|
|
2
|
+
import { db } from "../db/index.js";
|
|
3
|
+
import { passwordResetTokens, refreshTokens, userSessions } from "../db/schema.js";
|
|
4
|
+
const EMPTY = { refreshTokens: 0, sessions: 0, recoveryCredentials: 0 };
|
|
5
|
+
/**
|
|
6
|
+
* Revoke the user's refresh tokens, so no further access token can be minted
|
|
7
|
+
* from an existing session.
|
|
8
|
+
*/
|
|
9
|
+
export async function revokeRefreshTokens(userId, options = {}) {
|
|
10
|
+
const conditions = [eq(refreshTokens.userId, userId), isNull(refreshTokens.revokedAt)];
|
|
11
|
+
if (options.exceptRefreshTokenId) {
|
|
12
|
+
conditions.push(ne(refreshTokens.id, options.exceptRefreshTokenId));
|
|
13
|
+
}
|
|
14
|
+
const rows = await db
|
|
15
|
+
.update(refreshTokens)
|
|
16
|
+
.set({ revokedAt: new Date() })
|
|
17
|
+
.where(and(...conditions))
|
|
18
|
+
.returning({ id: refreshTokens.id });
|
|
19
|
+
return rows.length;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Revoke the user's authentication sessions.
|
|
23
|
+
*
|
|
24
|
+
* A revoked session stops authorising requests immediately; it is not merely
|
|
25
|
+
* hidden from a session list.
|
|
26
|
+
*/
|
|
27
|
+
export async function revokeAuthenticationSessions(userId, options = {}) {
|
|
28
|
+
const conditions = [eq(userSessions.userId, userId), isNull(userSessions.revokedAt)];
|
|
29
|
+
if (options.exceptSessionId) {
|
|
30
|
+
conditions.push(ne(userSessions.id, options.exceptSessionId));
|
|
31
|
+
}
|
|
32
|
+
const rows = await db
|
|
33
|
+
.update(userSessions)
|
|
34
|
+
.set({ revokedAt: new Date() })
|
|
35
|
+
.where(and(...conditions))
|
|
36
|
+
.returning({ id: userSessions.id });
|
|
37
|
+
return rows.length;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Revoke outstanding recovery credentials.
|
|
41
|
+
*
|
|
42
|
+
* A reset token already sitting in someone's inbox stays valid until it
|
|
43
|
+
* expires unless it is spent here. Someone who intercepted an earlier reset
|
|
44
|
+
* email could otherwise complete it after the user had already recovered.
|
|
45
|
+
*/
|
|
46
|
+
export async function revokeRecoveryCredentials(userId) {
|
|
47
|
+
const rows = await db
|
|
48
|
+
.update(passwordResetTokens)
|
|
49
|
+
.set({ usedAt: new Date() })
|
|
50
|
+
.where(and(eq(passwordResetTokens.userId, userId), isNull(passwordResetTokens.usedAt)))
|
|
51
|
+
.returning({ id: passwordResetTokens.id });
|
|
52
|
+
return rows.length;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Log the user out everywhere: sessions and refresh tokens together.
|
|
56
|
+
*
|
|
57
|
+
* This is what most credential changes want. Revoking only one leaves a live
|
|
58
|
+
* credential behind: a session with no refresh token still authorises requests
|
|
59
|
+
* until it expires, and a refresh token with no session can still mint access
|
|
60
|
+
* tokens.
|
|
61
|
+
*/
|
|
62
|
+
export async function revokeUserSessions(userId, options = {}) {
|
|
63
|
+
const sessions = await revokeAuthenticationSessions(userId, options);
|
|
64
|
+
const refreshTokensRevoked = await revokeRefreshTokens(userId, options);
|
|
65
|
+
return { ...EMPTY, sessions, refreshTokens: refreshTokensRevoked };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Everything: sessions, refresh tokens, and outstanding recovery credentials.
|
|
69
|
+
*
|
|
70
|
+
* Used after a password reset.
|
|
71
|
+
*
|
|
72
|
+
* API keys are deliberately **not** revoked. They are separately issued,
|
|
73
|
+
* long-lived credentials belonging to integrations rather than to the person,
|
|
74
|
+
* and silently killing them on a password reset breaks deployments without
|
|
75
|
+
* improving the account's own security. The residual gap is real — an API key
|
|
76
|
+
* minted by an attacker who already held the password survives — and the right
|
|
77
|
+
* answer is key expiry and rotation rather than coupling key lifetime to a
|
|
78
|
+
* human's password.
|
|
79
|
+
*/
|
|
80
|
+
export async function revokeAllUserCredentials(userId, options = {}) {
|
|
81
|
+
const sessions = await revokeAuthenticationSessions(userId, options);
|
|
82
|
+
const refreshTokensRevoked = await revokeRefreshTokens(userId, options);
|
|
83
|
+
const recoveryCredentials = await revokeRecoveryCredentials(userId);
|
|
84
|
+
return { sessions, refreshTokens: refreshTokensRevoked, recoveryCredentials };
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=sessionRevocation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionRevocation.js","sourceRoot":"","sources":["../../src/services/sessionRevocation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { magicLinks, passwordResetTokens, smsOtpCodes } from "../db/schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Atomic consumption of single-use credentials.
|
|
4
|
+
*
|
|
5
|
+
* A single-use credential has one dangerous shape, and it is always the same:
|
|
6
|
+
*
|
|
7
|
+
* ```text
|
|
8
|
+
* SELECT ... WHERE used_at IS NULL <- conditional
|
|
9
|
+
* if (!row) return
|
|
10
|
+
* UPDATE ... SET used_at = now() <- UNCONDITIONAL: the race
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Between the SELECT and the UPDATE, any number of concurrent requests can pass
|
|
14
|
+
* the same check. A magic link redeemed by ten parallel requests logs in ten
|
|
15
|
+
* times; a password reset token spent ten times over writes ten different
|
|
16
|
+
* passwords, last writer winning.
|
|
17
|
+
*
|
|
18
|
+
* The fix is to make the write itself the gate:
|
|
19
|
+
*
|
|
20
|
+
* ```text
|
|
21
|
+
* UPDATE ... SET used_at = now()
|
|
22
|
+
* WHERE token_hash = ? AND expires_at > now() AND used_at IS NULL
|
|
23
|
+
* RETURNING ...
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* PostgreSQL evaluates that predicate while holding a row lock, so exactly one
|
|
27
|
+
* transaction can update the row and see a returned row. Everyone else matches
|
|
28
|
+
* zero rows. The claim and the validation are one statement, so there is no
|
|
29
|
+
* window between them to lose.
|
|
30
|
+
*
|
|
31
|
+
* All consumption goes through here so the pattern exists once. A credential
|
|
32
|
+
* that consumes its own token inline is a credential that can be raced.
|
|
33
|
+
*/
|
|
34
|
+
export type ConsumeOutcome =
|
|
35
|
+
/** This caller claimed the credential. */
|
|
36
|
+
"consumed"
|
|
37
|
+
/** A row exists but was already spent. Somebody replayed it. */
|
|
38
|
+
| "replayed"
|
|
39
|
+
/** A row exists but has passed its expiry. */
|
|
40
|
+
| "expired"
|
|
41
|
+
/** No row for this value. */
|
|
42
|
+
| "not_found";
|
|
43
|
+
export interface ConsumeResult<T> {
|
|
44
|
+
outcome: ConsumeOutcome;
|
|
45
|
+
/** Present only when `outcome` is `consumed`. */
|
|
46
|
+
record?: T;
|
|
47
|
+
/**
|
|
48
|
+
* Why the credential was refused. Set for `replayed` and `expired`; omitted
|
|
49
|
+
* for `not_found`, so a caller cannot distinguish "never existed" from
|
|
50
|
+
* "deleted", which is itself information.
|
|
51
|
+
*/
|
|
52
|
+
reason?: "replayed" | "expired";
|
|
53
|
+
}
|
|
54
|
+
export type MagicLinkRow = typeof magicLinks.$inferSelect;
|
|
55
|
+
export type PasswordResetTokenRow = typeof passwordResetTokens.$inferSelect;
|
|
56
|
+
export type SmsOtpCodeRow = typeof smsOtpCodes.$inferSelect;
|
|
57
|
+
/** Atomically consume a magic link. */
|
|
58
|
+
export declare function consumeMagicLinkRow(tokenHash: string, now: Date): Promise<ConsumeResult<MagicLinkRow>>;
|
|
59
|
+
/** Atomically consume a password reset token. */
|
|
60
|
+
export declare function consumePasswordResetTokenRow(tokenHash: string, now: Date): Promise<ConsumeResult<PasswordResetTokenRow>>;
|
|
61
|
+
/**
|
|
62
|
+
* Atomically consume an SMS OTP code.
|
|
63
|
+
*
|
|
64
|
+
* Scoped to the user as well as the code. A code is six digits, so a lookup on
|
|
65
|
+
* the hash alone could let one user's code be spent against another's row.
|
|
66
|
+
*/
|
|
67
|
+
export declare function consumeSmsOtpCodeRow(userId: string, codeHash: string, now: Date): Promise<ConsumeResult<SmsOtpCodeRow>>;
|
|
68
|
+
//# sourceMappingURL=singleUse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleUse.d.ts","sourceRoot":"","sources":["../../src/services/singleUse.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,MAAM,cAAc;AACxB,0CAA0C;AACxC,UAAU;AACZ,gEAAgE;GAC9D,UAAU;AACZ,8CAA8C;GAC5C,SAAS;AACX,6BAA6B;GAC3B,WAAW,CAAC;AAEhB,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,OAAO,EAAE,cAAc,CAAC;IACxB,iDAAiD;IACjD,MAAM,CAAC,EAAE,CAAC,CAAC;IACX;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACjC;AAuED,MAAM,MAAM,YAAY,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC;AAC1D,MAAM,MAAM,qBAAqB,GAAG,OAAO,mBAAmB,CAAC,YAAY,CAAC;AAC5E,MAAM,MAAM,aAAa,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC;AAE5D,uCAAuC;AACvC,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,IAAI,GACR,OAAO,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAEtC;AAED,iDAAiD;AACjD,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,IAAI,GACR,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC,CAO/C;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,IAAI,GACR,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAQvC"}
|