@happyvertical/smrt-users 0.55.18 → 0.55.19
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/README.md +47 -0
- package/agents/oauth-authorization.md +124 -0
- package/agents/retention.md +18 -0
- package/dist/chunks/{TerminalAuthService-DjGsRtqb.js → TerminalAuthService-DWPeKl3k.js} +2 -2
- package/dist/chunks/TerminalAuthService-DWPeKl3k.js.map +1 -0
- package/dist/collections/OAuthAuthorizationCollection.d.ts +31 -0
- package/dist/collections/OAuthAuthorizationCollection.d.ts.map +1 -0
- package/dist/collections/index.d.ts +1 -0
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +832 -33
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +4590 -2529
- package/dist/models/OAuthAuthorization.d.ts +70 -0
- package/dist/models/OAuthAuthorization.d.ts.map +1 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/retention.d.ts +3 -1
- package/dist/retention.d.ts.map +1 -1
- package/dist/services/OAuthAuthorizationService.d.ts +52 -0
- package/dist/services/OAuthAuthorizationService.d.ts.map +1 -0
- package/dist/services/OAuthAuthorizationStorage.d.ts +39 -0
- package/dist/services/OAuthAuthorizationStorage.d.ts.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/oauth-retention.d.ts +14 -0
- package/dist/services/oauth-retention.d.ts.map +1 -0
- package/dist/services/oauth-transaction.d.ts +4 -0
- package/dist/services/oauth-transaction.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +925 -48
- package/dist/sveltekit/index.d.ts +1 -0
- package/dist/sveltekit/index.d.ts.map +1 -1
- package/dist/sveltekit/oauth-handlers.d.ts +25 -0
- package/dist/sveltekit/oauth-handlers.d.ts.map +1 -0
- package/dist/sveltekit.js +93 -25
- package/dist/sveltekit.js.map +1 -1
- package/package.json +13 -12
- package/dist/chunks/TerminalAuthService-DjGsRtqb.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
import { $ as DEFAULT_LOGIN_LOCKOUT_FACTOR, $t as UsersLoginAttemptCollection, A as readMobileBearerToken, At as deriveOperationPermissionCollectionName, B as DEFAULT_PIN_MANAGE_PERMISSION, Bt as UsersPasswordCredentialCollection, C as hasOperationPermission, Ct as applyTenantHierarchyUpdates, D as withSessionPermissionContext, Dt as DEFAULT_ROLE_PERMISSION_PATTERNS, E as withPrincipalPermissionContext, Et as recordTenantHierarchyChanges, F as encodeOidcTransaction, Ft as RolePermission, G as PIN_LOGIN_KIND, Gt as GroupRoleCollection, H as DeviceCredentialForbiddenError, Ht as MembershipOverrideCollection, I as getUsersOidcConfig, It as RoleCollection, J as ensurePinPermissionsRegistered, Jt as GroupMember, K as PIN_PERMISSION_DEFINITIONS, Kt as GroupRole, L as resolveOidcProviderConfig, Lt as UsersPinCredentialCollection, M as OidcLoginError, Mt as normalizeOperationPermissionAction, N as OidcLoginService, Nt as registerPermissionDefinitions, O as MobileAuthError, Ot as RolePermissionCollection, P as decodeOidcTransaction, Pt as syncPermissionCatalog, Q as DEFAULT_LOGIN_LOCKOUT_BASE_SECONDS, Qt as UsersLoginAuditEvent, Rt as UsersPinCredential, S as checkOperationPermission, St as TenantHierarchyError, T as getRequestScopedDatabase, Tt as planTenantHierarchy, U as DeviceCredentialPolicyError, Ut as MembershipOverride, V as DeviceCredentialError, Vt as UsersPasswordCredential, W as DeviceCredentialService, Wt as MembershipCollection, X as PermissionResolver, Xt as Group, Y as SessionService, Yt as GroupCollection, Z as DEFAULT_LOGIN_ATTEMPT_WINDOW_SECONDS, Zt as UsersLoginAuditEventCollection, _ as PasswordCredentialService, _t as TenantPermissionOverrideCollection, a as TerminalAuthError, an as isValidEmail, at as LoginRateLimitError, b as OperationPermissionError, bt as Tenant, c as DEFAULT_PASSWORD_DENYLIST, cn as SESSION_DATA_KEYS, ct as DEFAULT_ANCESTOR_READ_MAX_DEPTH, d as DEFAULT_PASSWORD_MIN_LENGTH, dn as Role, dt as normalizeAncestorReadPolicy, en as UsersLoginAttempt, et as DEFAULT_LOGIN_LOCKOUT_MAX_SECONDS, f as PASSWORD_LOGIN_KIND, fn as Permission, ft as OidcProvisioningError, g as PasswordCredentialForbiddenError, gt as backfillUserEmailKeys, h as PasswordCredentialError, ht as UserEmailKeyBackfillError, i as TERMINAL_APPROVE_LOGIN_KIND, in as User, it as LoginAttemptLimiter, j as validateMobileRedirectUri, jt as deriveOperationPermissionSlug, k as MobileAuthService, kt as PermissionCatalogService, l as DEFAULT_PASSWORD_MANAGE_PERMISSION, ln as Session, lt as getConfiguredAncestorReadPolicy, m as PASSWORD_PERMISSION_DEFINITIONS, n as DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, nn as SessionCollection, nt as DurableLoginAuditSink, o as TerminalAuthRateLimitError, on as normalizeEmail, ot as withoutListBounds, p as PASSWORD_MAX_LENGTH_CEILING, pn as Membership, pt as UserCollection, q as PinVerifier, qt as GroupMemberCollection, r as DEFAULT_CLI_SESSION_TTL_SECONDS, rn as UsersCliAuthRequest, rt as InvalidCredentialsError, s as TerminalAuthService, sn as DEFAULT_SESSION_TTL, st as ANCESTOR_READ_ACTION, t as DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, tn as UsersCliAuthRequestCollection, tt as DEFAULT_LOGIN_MAX_ATTEMPTS, u as DEFAULT_PASSWORD_MAX_LENGTH, un as generateSessionId, ut as isAncestorReadableSlug, v as PasswordPolicyError, vt as TenantPermissionOverride, w as getCurrentSessionPermissionContext, wt as assertTenantTableName, x as assertOperationPermission, xt as MAX_TENANT_HIERARCHY_DEPTH, y as ensurePasswordPermissionsRegistered, yt as TenantCollection, z as DEFAULT_DEVICE_PERSON_IDLE_SECONDS, zt as PermissionCollection } from "./chunks/TerminalAuthService-
|
|
1
|
+
import { $ as DEFAULT_LOGIN_LOCKOUT_FACTOR, $t as UsersLoginAttemptCollection, A as readMobileBearerToken, At as deriveOperationPermissionCollectionName, B as DEFAULT_PIN_MANAGE_PERMISSION, Bt as UsersPasswordCredentialCollection, C as hasOperationPermission, Ct as applyTenantHierarchyUpdates, D as withSessionPermissionContext, Dt as DEFAULT_ROLE_PERMISSION_PATTERNS, E as withPrincipalPermissionContext, Et as recordTenantHierarchyChanges, F as encodeOidcTransaction, Ft as RolePermission, G as PIN_LOGIN_KIND, Gt as GroupRoleCollection, H as DeviceCredentialForbiddenError, Ht as MembershipOverrideCollection, I as getUsersOidcConfig, It as RoleCollection, J as ensurePinPermissionsRegistered, Jt as GroupMember, K as PIN_PERMISSION_DEFINITIONS, Kt as GroupRole, L as resolveOidcProviderConfig, Lt as UsersPinCredentialCollection, M as OidcLoginError, Mt as normalizeOperationPermissionAction, N as OidcLoginService, Nt as registerPermissionDefinitions, O as MobileAuthError, Ot as RolePermissionCollection, P as decodeOidcTransaction, Pt as syncPermissionCatalog, Q as DEFAULT_LOGIN_LOCKOUT_BASE_SECONDS, Qt as UsersLoginAuditEvent, Rt as UsersPinCredential, S as checkOperationPermission, St as TenantHierarchyError, T as getRequestScopedDatabase, Tt as planTenantHierarchy, U as DeviceCredentialPolicyError, Ut as MembershipOverride, V as DeviceCredentialError, Vt as UsersPasswordCredential, W as DeviceCredentialService, Wt as MembershipCollection, X as PermissionResolver, Xt as Group, Y as SessionService, Yt as GroupCollection, Z as DEFAULT_LOGIN_ATTEMPT_WINDOW_SECONDS, Zt as UsersLoginAuditEventCollection, _ as PasswordCredentialService, _t as TenantPermissionOverrideCollection, a as TerminalAuthError, an as isValidEmail, at as LoginRateLimitError, b as OperationPermissionError, bt as Tenant, c as DEFAULT_PASSWORD_DENYLIST, cn as SESSION_DATA_KEYS, ct as DEFAULT_ANCESTOR_READ_MAX_DEPTH, d as DEFAULT_PASSWORD_MIN_LENGTH, dn as Role, dt as normalizeAncestorReadPolicy, en as UsersLoginAttempt, et as DEFAULT_LOGIN_LOCKOUT_MAX_SECONDS, f as PASSWORD_LOGIN_KIND, fn as Permission, ft as OidcProvisioningError, g as PasswordCredentialForbiddenError, gt as backfillUserEmailKeys, h as PasswordCredentialError, ht as UserEmailKeyBackfillError, i as TERMINAL_APPROVE_LOGIN_KIND, in as User, it as LoginAttemptLimiter, j as validateMobileRedirectUri, jt as deriveOperationPermissionSlug, k as MobileAuthService, kt as PermissionCatalogService, l as DEFAULT_PASSWORD_MANAGE_PERMISSION, ln as Session, lt as getConfiguredAncestorReadPolicy, m as PASSWORD_PERMISSION_DEFINITIONS, n as DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, nn as SessionCollection, nt as DurableLoginAuditSink, o as TerminalAuthRateLimitError, on as normalizeEmail, ot as withoutListBounds, p as PASSWORD_MAX_LENGTH_CEILING, pn as Membership, pt as UserCollection, q as PinVerifier, qt as GroupMemberCollection, r as DEFAULT_CLI_SESSION_TTL_SECONDS, rn as UsersCliAuthRequest, rt as InvalidCredentialsError, s as TerminalAuthService, sn as DEFAULT_SESSION_TTL, st as ANCESTOR_READ_ACTION, t as DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, tn as UsersCliAuthRequestCollection, tt as DEFAULT_LOGIN_MAX_ATTEMPTS, u as DEFAULT_PASSWORD_MAX_LENGTH, un as generateSessionId, ut as isAncestorReadableSlug, v as PasswordPolicyError, vt as TenantPermissionOverride, w as getCurrentSessionPermissionContext, wt as assertTenantTableName, x as assertOperationPermission, xt as MAX_TENANT_HIERARCHY_DEPTH, y as ensurePasswordPermissionsRegistered, yt as TenantCollection, z as DEFAULT_DEVICE_PERSON_IDLE_SECONDS, zt as PermissionCollection } from "./chunks/TerminalAuthService-DWPeKl3k.js";
|
|
2
2
|
import { a as MembershipStatus, c as TenantPermissionEffect, i as DEFAULT_TENANT_POLICY, l as TenantStatus, n as DEFAULT_ROLES, o as OverrideEffect, r as DEFAULT_ROLE_SLUGS, s as SessionStatus, t as AccessRequestStatus, u as UserStatus } from "./chunks/types-DFqeph0R.js";
|
|
3
3
|
import { ObjectRegistry, SmrtCollection, SmrtObject, SmrtRecipe, bumpChangeFeed, ensureChangeFeedTable, field, findManifestEntryByQualifiedName, foreignKey, isEmbeddedDatabase, isUniqueViolationError, registerRetentionTask, smrt, unregisterRetentionTask, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
|
|
4
4
|
import { PROFILE_EMAIL_KEY_BACKFILL_NAME, Person, ProfileCollection, ProfileTypeCollection, normalizeIdentityEmail } from "@happyvertical/smrt-profiles";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
+
import { setTimeout } from "node:timers/promises";
|
|
6
7
|
import { createLogger } from "@happyvertical/logger";
|
|
7
8
|
import { TenantScoped, tenantId, withSystemContext } from "@happyvertical/smrt-tenancy";
|
|
8
9
|
import { BackfillTracker } from "@happyvertical/smrt-core/migrations";
|
|
10
|
+
import { OAuthServerError } from "@happyvertical/auth/server";
|
|
9
11
|
//#region src/models/ResourceGrant.ts
|
|
10
|
-
var __defProp$
|
|
11
|
-
var __getOwnPropDesc$
|
|
12
|
-
var __defNormalProp$
|
|
12
|
+
var __defProp$5 = Object.defineProperty;
|
|
13
|
+
var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
|
|
14
|
+
var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, {
|
|
13
15
|
enumerable: true,
|
|
14
16
|
configurable: true,
|
|
15
17
|
writable: true,
|
|
16
18
|
value
|
|
17
19
|
}) : obj[key] = value;
|
|
18
|
-
var __decorateClass$
|
|
19
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
20
|
+
var __decorateClass$5 = (decorators, target, key, kind) => {
|
|
21
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
|
|
20
22
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
21
|
-
if (kind && result) __defProp$
|
|
23
|
+
if (kind && result) __defProp$5(target, key, result);
|
|
22
24
|
return result;
|
|
23
25
|
};
|
|
24
|
-
var __publicField$
|
|
26
|
+
var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
25
27
|
var ResourceGrant = class extends SmrtObject {
|
|
26
28
|
tenantId;
|
|
27
29
|
userId;
|
|
@@ -48,32 +50,32 @@ var ResourceGrant = class extends SmrtObject {
|
|
|
48
50
|
return !this.revokedAt;
|
|
49
51
|
}
|
|
50
52
|
};
|
|
51
|
-
__publicField$
|
|
52
|
-
__decorateClass$
|
|
53
|
-
__decorateClass$
|
|
54
|
-
__decorateClass$
|
|
53
|
+
__publicField$5(ResourceGrant, "__smrtPackage__", "@happyvertical/smrt-users");
|
|
54
|
+
__decorateClass$5([foreignKey("Tenant", { required: true })], ResourceGrant.prototype, "tenantId", 2);
|
|
55
|
+
__decorateClass$5([field({ description: "The person receiving this record-specific access rule." }), foreignKey("User", { required: true })], ResourceGrant.prototype, "userId", 2);
|
|
56
|
+
__decorateClass$5([field({
|
|
55
57
|
description: "The kind of record this access rule applies to.",
|
|
56
58
|
type: "text"
|
|
57
59
|
})], ResourceGrant.prototype, "resourceType", 2);
|
|
58
|
-
__decorateClass$
|
|
60
|
+
__decorateClass$5([field({
|
|
59
61
|
description: "The specific record covered by this access rule.",
|
|
60
62
|
type: "text"
|
|
61
63
|
})], ResourceGrant.prototype, "resourceId", 2);
|
|
62
|
-
__decorateClass$
|
|
64
|
+
__decorateClass$5([field({
|
|
63
65
|
description: "The action this rule allows or denies for the record.",
|
|
64
66
|
type: "text"
|
|
65
67
|
})], ResourceGrant.prototype, "permission", 2);
|
|
66
|
-
__decorateClass$
|
|
68
|
+
__decorateClass$5([field({
|
|
67
69
|
description: "Whether this rule grants access or denies it. Denies take precedence.",
|
|
68
70
|
type: "text"
|
|
69
71
|
})], ResourceGrant.prototype, "effect", 2);
|
|
70
|
-
__decorateClass$
|
|
71
|
-
__decorateClass$
|
|
72
|
-
__decorateClass$
|
|
72
|
+
__decorateClass$5([field({ description: "Whether the recipient may pass this grant on to another person." })], ResourceGrant.prototype, "canDelegate", 2);
|
|
73
|
+
__decorateClass$5([field({ description: "The earlier grant from which this access was delegated." }), foreignKey("ResourceGrant")], ResourceGrant.prototype, "parentGrantId", 2);
|
|
74
|
+
__decorateClass$5([field({
|
|
73
75
|
description: "When this access rule was revoked, if it has ended.",
|
|
74
76
|
type: "text"
|
|
75
77
|
})], ResourceGrant.prototype, "revokedAt", 2);
|
|
76
|
-
ResourceGrant = __decorateClass$
|
|
78
|
+
ResourceGrant = __decorateClass$5([smrt({
|
|
77
79
|
tenantScoped: true,
|
|
78
80
|
api: { include: [] },
|
|
79
81
|
mcp: { include: [] },
|
|
@@ -179,21 +181,21 @@ var UsersRolesAndPermissionsRecipe = class extends SmrtRecipe {
|
|
|
179
181
|
};
|
|
180
182
|
//#endregion
|
|
181
183
|
//#region src/models/MagicLinkToken.ts
|
|
182
|
-
var __defProp$
|
|
183
|
-
var __getOwnPropDesc$
|
|
184
|
-
var __defNormalProp$
|
|
184
|
+
var __defProp$4 = Object.defineProperty;
|
|
185
|
+
var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
|
|
186
|
+
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, {
|
|
185
187
|
enumerable: true,
|
|
186
188
|
configurable: true,
|
|
187
189
|
writable: true,
|
|
188
190
|
value
|
|
189
191
|
}) : obj[key] = value;
|
|
190
|
-
var __decorateClass$
|
|
191
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
192
|
+
var __decorateClass$4 = (decorators, target, key, kind) => {
|
|
193
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
|
|
192
194
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
193
|
-
if (kind && result) __defProp$
|
|
195
|
+
if (kind && result) __defProp$4(target, key, result);
|
|
194
196
|
return result;
|
|
195
197
|
};
|
|
196
|
-
var __publicField$
|
|
198
|
+
var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
197
199
|
var DEFAULT_TOKEN_EXPIRY_SECONDS = 600;
|
|
198
200
|
var UsersMagicLinkToken = class extends SmrtObject {
|
|
199
201
|
nonce = "";
|
|
@@ -218,16 +220,16 @@ var UsersMagicLinkToken = class extends SmrtObject {
|
|
|
218
220
|
return !this.used && !this.isExpired();
|
|
219
221
|
}
|
|
220
222
|
};
|
|
221
|
-
__publicField$
|
|
222
|
-
__decorateClass$
|
|
223
|
+
__publicField$4(UsersMagicLinkToken, "__smrtPackage__", "@happyvertical/smrt-users");
|
|
224
|
+
__decorateClass$4([field({
|
|
223
225
|
required: true,
|
|
224
226
|
unique: true
|
|
225
227
|
})], UsersMagicLinkToken.prototype, "nonce", 2);
|
|
226
|
-
__decorateClass$
|
|
228
|
+
__decorateClass$4([field({
|
|
227
229
|
type: "datetime",
|
|
228
230
|
indexed: true
|
|
229
231
|
})], UsersMagicLinkToken.prototype, "expiresAt", 2);
|
|
230
|
-
UsersMagicLinkToken = __decorateClass$
|
|
232
|
+
UsersMagicLinkToken = __decorateClass$4([smrt({
|
|
231
233
|
tableName: "users_magic_link_tokens",
|
|
232
234
|
sensitive: true,
|
|
233
235
|
api: { include: [] },
|
|
@@ -279,6 +281,395 @@ var UsersMagicLinkTokenCollection = class extends SmrtCollection {
|
|
|
279
281
|
}
|
|
280
282
|
};
|
|
281
283
|
//#endregion
|
|
284
|
+
//#region src/models/OAuthAuthorization.ts
|
|
285
|
+
var __defProp$3 = Object.defineProperty;
|
|
286
|
+
var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
|
|
287
|
+
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, {
|
|
288
|
+
enumerable: true,
|
|
289
|
+
configurable: true,
|
|
290
|
+
writable: true,
|
|
291
|
+
value
|
|
292
|
+
}) : obj[key] = value;
|
|
293
|
+
var __decorateClass$3 = (decorators, target, key, kind) => {
|
|
294
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
|
|
295
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
296
|
+
if (kind && result) __defProp$3(target, key, result);
|
|
297
|
+
return result;
|
|
298
|
+
};
|
|
299
|
+
var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
300
|
+
function stringArray(value) {
|
|
301
|
+
if (Array.isArray(value) && value.every((item) => typeof item === "string")) return [...value];
|
|
302
|
+
if (typeof value !== "string") return [];
|
|
303
|
+
try {
|
|
304
|
+
const parsed = JSON.parse(value);
|
|
305
|
+
return Array.isArray(parsed) && parsed.every((item) => typeof item === "string") ? parsed : [];
|
|
306
|
+
} catch {
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function tokenClaims(value) {
|
|
311
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return Object.fromEntries(Object.entries(value).filter(([, item]) => typeof item === "string" || typeof item === "number" || typeof item === "boolean"));
|
|
312
|
+
if (typeof value !== "string") return {};
|
|
313
|
+
try {
|
|
314
|
+
return tokenClaims(JSON.parse(value));
|
|
315
|
+
} catch {
|
|
316
|
+
return {};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
var UsersOAuthClient = class extends SmrtObject {
|
|
320
|
+
clientId = "";
|
|
321
|
+
redirectUris = "[]";
|
|
322
|
+
allowedScopes = "[]";
|
|
323
|
+
tokenEndpointAuthMethod = "none";
|
|
324
|
+
secretHash = null;
|
|
325
|
+
allowedResources = "null";
|
|
326
|
+
createdAt = /* @__PURE__ */ new Date();
|
|
327
|
+
getRedirectUris() {
|
|
328
|
+
return stringArray(this.redirectUris);
|
|
329
|
+
}
|
|
330
|
+
getAllowedScopes() {
|
|
331
|
+
return stringArray(this.allowedScopes);
|
|
332
|
+
}
|
|
333
|
+
getAllowedResources() {
|
|
334
|
+
return this.allowedResources === "null" ? void 0 : stringArray(this.allowedResources);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
__publicField$3(UsersOAuthClient, "__smrtPackage__", "@happyvertical/smrt-users");
|
|
338
|
+
__decorateClass$3([field({
|
|
339
|
+
type: "text",
|
|
340
|
+
required: true,
|
|
341
|
+
unique: true
|
|
342
|
+
})], UsersOAuthClient.prototype, "clientId", 2);
|
|
343
|
+
__decorateClass$3([field({
|
|
344
|
+
nullable: true,
|
|
345
|
+
sensitive: true
|
|
346
|
+
})], UsersOAuthClient.prototype, "secretHash", 2);
|
|
347
|
+
__decorateClass$3([field({ type: "datetime" })], UsersOAuthClient.prototype, "createdAt", 2);
|
|
348
|
+
UsersOAuthClient = __decorateClass$3([smrt({
|
|
349
|
+
tableName: "users_oauth_clients",
|
|
350
|
+
sensitive: true,
|
|
351
|
+
api: { include: [] },
|
|
352
|
+
mcp: { include: [] },
|
|
353
|
+
cli: false
|
|
354
|
+
})], UsersOAuthClient);
|
|
355
|
+
var UsersOAuthAuthorizationCode = class extends SmrtObject {
|
|
356
|
+
authorizationId = null;
|
|
357
|
+
codeHash = "";
|
|
358
|
+
clientId = "";
|
|
359
|
+
subject = "";
|
|
360
|
+
tenantId = null;
|
|
361
|
+
claims = "{}";
|
|
362
|
+
redirectUri = "";
|
|
363
|
+
scopes = "[]";
|
|
364
|
+
resource = null;
|
|
365
|
+
codeChallenge = "";
|
|
366
|
+
expiresAt = /* @__PURE__ */ new Date();
|
|
367
|
+
consumedAt = null;
|
|
368
|
+
getScopes() {
|
|
369
|
+
return stringArray(this.scopes);
|
|
370
|
+
}
|
|
371
|
+
getClaims() {
|
|
372
|
+
return tokenClaims(this.claims);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
__publicField$3(UsersOAuthAuthorizationCode, "__smrtPackage__", "@happyvertical/smrt-users");
|
|
376
|
+
__decorateClass$3([field({
|
|
377
|
+
nullable: true,
|
|
378
|
+
indexed: true
|
|
379
|
+
})], UsersOAuthAuthorizationCode.prototype, "authorizationId", 2);
|
|
380
|
+
__decorateClass$3([field({
|
|
381
|
+
type: "text",
|
|
382
|
+
required: true,
|
|
383
|
+
unique: true,
|
|
384
|
+
sensitive: true
|
|
385
|
+
})], UsersOAuthAuthorizationCode.prototype, "codeHash", 2);
|
|
386
|
+
__decorateClass$3([field({
|
|
387
|
+
type: "text",
|
|
388
|
+
indexed: true
|
|
389
|
+
})], UsersOAuthAuthorizationCode.prototype, "clientId", 2);
|
|
390
|
+
__decorateClass$3([field({
|
|
391
|
+
type: "text",
|
|
392
|
+
indexed: true
|
|
393
|
+
})], UsersOAuthAuthorizationCode.prototype, "subject", 2);
|
|
394
|
+
__decorateClass$3([field({
|
|
395
|
+
nullable: true,
|
|
396
|
+
indexed: true
|
|
397
|
+
})], UsersOAuthAuthorizationCode.prototype, "tenantId", 2);
|
|
398
|
+
__decorateClass$3([field({ nullable: true })], UsersOAuthAuthorizationCode.prototype, "resource", 2);
|
|
399
|
+
__decorateClass$3([field({
|
|
400
|
+
type: "datetime",
|
|
401
|
+
indexed: true
|
|
402
|
+
})], UsersOAuthAuthorizationCode.prototype, "expiresAt", 2);
|
|
403
|
+
__decorateClass$3([field({
|
|
404
|
+
type: "datetime",
|
|
405
|
+
nullable: true
|
|
406
|
+
})], UsersOAuthAuthorizationCode.prototype, "consumedAt", 2);
|
|
407
|
+
UsersOAuthAuthorizationCode = __decorateClass$3([smrt({
|
|
408
|
+
tableName: "users_oauth_authorization_codes",
|
|
409
|
+
sensitive: true,
|
|
410
|
+
api: { include: [] },
|
|
411
|
+
mcp: { include: [] },
|
|
412
|
+
cli: false
|
|
413
|
+
})], UsersOAuthAuthorizationCode);
|
|
414
|
+
var UsersOAuthRefreshGrant = class extends SmrtObject {
|
|
415
|
+
authorizationId = null;
|
|
416
|
+
tokenHash = "";
|
|
417
|
+
familyId = "";
|
|
418
|
+
clientId = "";
|
|
419
|
+
subject = "";
|
|
420
|
+
tenantId = null;
|
|
421
|
+
claims = "{}";
|
|
422
|
+
scopes = "[]";
|
|
423
|
+
resource = null;
|
|
424
|
+
expiresAt = /* @__PURE__ */ new Date();
|
|
425
|
+
consumedAt = null;
|
|
426
|
+
revokedAt = null;
|
|
427
|
+
getScopes() {
|
|
428
|
+
return stringArray(this.scopes);
|
|
429
|
+
}
|
|
430
|
+
getClaims() {
|
|
431
|
+
return tokenClaims(this.claims);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
__publicField$3(UsersOAuthRefreshGrant, "__smrtPackage__", "@happyvertical/smrt-users");
|
|
435
|
+
__decorateClass$3([field({
|
|
436
|
+
nullable: true,
|
|
437
|
+
indexed: true
|
|
438
|
+
})], UsersOAuthRefreshGrant.prototype, "authorizationId", 2);
|
|
439
|
+
__decorateClass$3([field({
|
|
440
|
+
type: "text",
|
|
441
|
+
required: true,
|
|
442
|
+
unique: true,
|
|
443
|
+
sensitive: true
|
|
444
|
+
})], UsersOAuthRefreshGrant.prototype, "tokenHash", 2);
|
|
445
|
+
__decorateClass$3([field({
|
|
446
|
+
type: "text",
|
|
447
|
+
indexed: true
|
|
448
|
+
})], UsersOAuthRefreshGrant.prototype, "familyId", 2);
|
|
449
|
+
__decorateClass$3([field({
|
|
450
|
+
type: "text",
|
|
451
|
+
indexed: true
|
|
452
|
+
})], UsersOAuthRefreshGrant.prototype, "clientId", 2);
|
|
453
|
+
__decorateClass$3([field({
|
|
454
|
+
type: "text",
|
|
455
|
+
indexed: true
|
|
456
|
+
})], UsersOAuthRefreshGrant.prototype, "subject", 2);
|
|
457
|
+
__decorateClass$3([field({
|
|
458
|
+
nullable: true,
|
|
459
|
+
indexed: true
|
|
460
|
+
})], UsersOAuthRefreshGrant.prototype, "tenantId", 2);
|
|
461
|
+
__decorateClass$3([field({ nullable: true })], UsersOAuthRefreshGrant.prototype, "resource", 2);
|
|
462
|
+
__decorateClass$3([field({
|
|
463
|
+
type: "datetime",
|
|
464
|
+
indexed: true
|
|
465
|
+
})], UsersOAuthRefreshGrant.prototype, "expiresAt", 2);
|
|
466
|
+
__decorateClass$3([field({
|
|
467
|
+
type: "datetime",
|
|
468
|
+
nullable: true
|
|
469
|
+
})], UsersOAuthRefreshGrant.prototype, "consumedAt", 2);
|
|
470
|
+
__decorateClass$3([field({
|
|
471
|
+
type: "datetime",
|
|
472
|
+
nullable: true,
|
|
473
|
+
indexed: true
|
|
474
|
+
})], UsersOAuthRefreshGrant.prototype, "revokedAt", 2);
|
|
475
|
+
UsersOAuthRefreshGrant = __decorateClass$3([smrt({
|
|
476
|
+
tableName: "users_oauth_refresh_grants",
|
|
477
|
+
sensitive: true,
|
|
478
|
+
api: { include: [] },
|
|
479
|
+
mcp: { include: [] },
|
|
480
|
+
cli: false
|
|
481
|
+
})], UsersOAuthRefreshGrant);
|
|
482
|
+
var UsersOAuthAccessTokenRevocation = class extends SmrtObject {
|
|
483
|
+
jti = "";
|
|
484
|
+
expiresAt = /* @__PURE__ */ new Date();
|
|
485
|
+
};
|
|
486
|
+
__publicField$3(UsersOAuthAccessTokenRevocation, "__smrtPackage__", "@happyvertical/smrt-users");
|
|
487
|
+
__decorateClass$3([field({
|
|
488
|
+
type: "text",
|
|
489
|
+
required: true,
|
|
490
|
+
unique: true,
|
|
491
|
+
sensitive: true
|
|
492
|
+
})], UsersOAuthAccessTokenRevocation.prototype, "jti", 2);
|
|
493
|
+
__decorateClass$3([field({
|
|
494
|
+
type: "datetime",
|
|
495
|
+
indexed: true
|
|
496
|
+
})], UsersOAuthAccessTokenRevocation.prototype, "expiresAt", 2);
|
|
497
|
+
UsersOAuthAccessTokenRevocation = __decorateClass$3([smrt({
|
|
498
|
+
tableName: "users_oauth_access_token_revocations",
|
|
499
|
+
sensitive: true,
|
|
500
|
+
api: { include: [] },
|
|
501
|
+
mcp: { include: [] },
|
|
502
|
+
cli: false
|
|
503
|
+
})], UsersOAuthAccessTokenRevocation);
|
|
504
|
+
var UsersOAuthAuthorization = class extends SmrtObject {
|
|
505
|
+
subject = "";
|
|
506
|
+
sessionId = "";
|
|
507
|
+
clientId = "";
|
|
508
|
+
tenantId = null;
|
|
509
|
+
scopes = "[]";
|
|
510
|
+
permissionCeiling = "[]";
|
|
511
|
+
resource = null;
|
|
512
|
+
revokedAt = null;
|
|
513
|
+
getPermissionCeiling() {
|
|
514
|
+
return stringArray(this.permissionCeiling);
|
|
515
|
+
}
|
|
516
|
+
getScopes() {
|
|
517
|
+
return stringArray(this.scopes);
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
__publicField$3(UsersOAuthAuthorization, "__smrtPackage__", "@happyvertical/smrt-users");
|
|
521
|
+
__decorateClass$3([field({
|
|
522
|
+
type: "text",
|
|
523
|
+
indexed: true
|
|
524
|
+
})], UsersOAuthAuthorization.prototype, "subject", 2);
|
|
525
|
+
__decorateClass$3([field({
|
|
526
|
+
type: "text",
|
|
527
|
+
sensitive: true
|
|
528
|
+
})], UsersOAuthAuthorization.prototype, "sessionId", 2);
|
|
529
|
+
__decorateClass$3([field({
|
|
530
|
+
type: "text",
|
|
531
|
+
indexed: true
|
|
532
|
+
})], UsersOAuthAuthorization.prototype, "clientId", 2);
|
|
533
|
+
__decorateClass$3([field({ nullable: true })], UsersOAuthAuthorization.prototype, "tenantId", 2);
|
|
534
|
+
__decorateClass$3([field({ nullable: true })], UsersOAuthAuthorization.prototype, "resource", 2);
|
|
535
|
+
__decorateClass$3([field({
|
|
536
|
+
type: "datetime",
|
|
537
|
+
nullable: true
|
|
538
|
+
})], UsersOAuthAuthorization.prototype, "revokedAt", 2);
|
|
539
|
+
UsersOAuthAuthorization = __decorateClass$3([smrt({
|
|
540
|
+
tableName: "users_oauth_authorizations",
|
|
541
|
+
sensitive: true,
|
|
542
|
+
api: { include: [] },
|
|
543
|
+
mcp: { include: [] },
|
|
544
|
+
cli: false
|
|
545
|
+
})], UsersOAuthAuthorization);
|
|
546
|
+
var UsersOAuthRefreshFamily = class extends SmrtObject {
|
|
547
|
+
authorizationId = null;
|
|
548
|
+
revokedAt = null;
|
|
549
|
+
};
|
|
550
|
+
__publicField$3(UsersOAuthRefreshFamily, "__smrtPackage__", "@happyvertical/smrt-users");
|
|
551
|
+
__decorateClass$3([field({
|
|
552
|
+
nullable: true,
|
|
553
|
+
indexed: true
|
|
554
|
+
})], UsersOAuthRefreshFamily.prototype, "authorizationId", 2);
|
|
555
|
+
__decorateClass$3([field({
|
|
556
|
+
type: "datetime",
|
|
557
|
+
nullable: true
|
|
558
|
+
})], UsersOAuthRefreshFamily.prototype, "revokedAt", 2);
|
|
559
|
+
UsersOAuthRefreshFamily = __decorateClass$3([smrt({
|
|
560
|
+
tableName: "users_oauth_refresh_families",
|
|
561
|
+
sensitive: true,
|
|
562
|
+
api: { include: [] },
|
|
563
|
+
mcp: { include: [] },
|
|
564
|
+
cli: false
|
|
565
|
+
})], UsersOAuthRefreshFamily);
|
|
566
|
+
//#endregion
|
|
567
|
+
//#region src/collections/OAuthAuthorizationCollection.ts
|
|
568
|
+
var UsersOAuthClientCollection = class extends SmrtCollection {
|
|
569
|
+
static _itemClass = UsersOAuthClient;
|
|
570
|
+
findByClientId(clientId) {
|
|
571
|
+
return this.findOne({ where: { clientId } });
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
var UsersOAuthAuthorizationCodeCollection = class extends SmrtCollection {
|
|
575
|
+
static _itemClass = UsersOAuthAuthorizationCode;
|
|
576
|
+
async consume(id, codeHash, clientId, redirectUri, resource, now) {
|
|
577
|
+
return (await this.db.query(`UPDATE ${this.tableName} SET consumed_at = ?, updated_at = ? WHERE id = ? AND code_hash = ? AND client_id = ? AND redirect_uri = ? AND ${resource === null ? "resource IS NULL" : "resource = ?"} AND consumed_at IS NULL AND expires_at > ?`, now.toISOString(), now.toISOString(), id, codeHash, clientId, redirectUri, ...resource === null ? [] : [resource], now.toISOString())).rowCount === 1;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
var UsersOAuthRefreshGrantCollection = class extends SmrtCollection {
|
|
581
|
+
static _itemClass = UsersOAuthRefreshGrant;
|
|
582
|
+
async consume(id, tokenHash, clientId, resource, now) {
|
|
583
|
+
return (await this.db.query(`UPDATE ${this.tableName} SET consumed_at = ?, updated_at = ? WHERE id = ? AND token_hash = ? AND client_id = ? AND ${resource === null ? "resource IS NULL" : "resource = ?"} AND consumed_at IS NULL AND revoked_at IS NULL AND expires_at > ?`, now.toISOString(), now.toISOString(), id, tokenHash, clientId, ...resource === null ? [] : [resource], now.toISOString())).rowCount === 1;
|
|
584
|
+
}
|
|
585
|
+
async revokeFamily(familyId, now) {
|
|
586
|
+
await this.db.query(`UPDATE ${this.tableName} SET revoked_at = ?, updated_at = ? WHERE family_id = ? AND revoked_at IS NULL`, now.toISOString(), now.toISOString(), familyId);
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
var UsersOAuthAccessTokenRevocationCollection = class extends SmrtCollection {
|
|
590
|
+
static _itemClass = UsersOAuthAccessTokenRevocation;
|
|
591
|
+
async findByJti(jti) {
|
|
592
|
+
const result = await this.db.query(`SELECT id FROM ${this.tableName} WHERE jti = ?`, jti);
|
|
593
|
+
return result.rows[0] ? this.get(String(result.rows[0].id)) : null;
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
var UsersOAuthAuthorizationCollection = class extends SmrtCollection {
|
|
597
|
+
static _itemClass = UsersOAuthAuthorization;
|
|
598
|
+
};
|
|
599
|
+
var UsersOAuthRefreshFamilyCollection = class extends SmrtCollection {
|
|
600
|
+
static _itemClass = UsersOAuthRefreshFamily;
|
|
601
|
+
async lock(id, now) {
|
|
602
|
+
return (await this.db.query(`UPDATE ${this.tableName} SET updated_at = ? WHERE id = ?`, now.toISOString(), id)).rowCount === 1;
|
|
603
|
+
}
|
|
604
|
+
async revoke(id, now) {
|
|
605
|
+
await this.db.query(`UPDATE ${this.tableName} SET revoked_at = ?, updated_at = ? WHERE id = ?`, now.toISOString(), now.toISOString(), id);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
//#endregion
|
|
609
|
+
//#region src/services/oauth-transaction.ts
|
|
610
|
+
async function withOAuthTransaction(db, action) {
|
|
611
|
+
if (!db.transaction) throw new Error("OAuth persistence requires transaction support.");
|
|
612
|
+
for (let attempt = 0;; attempt++) try {
|
|
613
|
+
return await db.transaction(action);
|
|
614
|
+
} catch (error) {
|
|
615
|
+
let current = error;
|
|
616
|
+
let retryable = false;
|
|
617
|
+
let invalidated = false;
|
|
618
|
+
for (let depth = 0; depth < 4 && current && typeof current === "object"; depth++) {
|
|
619
|
+
const failure = current;
|
|
620
|
+
if ([
|
|
621
|
+
"SQLITE_BUSY",
|
|
622
|
+
"SQLITE_BUSY_SNAPSHOT",
|
|
623
|
+
"40001",
|
|
624
|
+
"40P01"
|
|
625
|
+
].includes(String(failure.code))) retryable = true;
|
|
626
|
+
invalidated ||= failure.connectionInvalidated === true;
|
|
627
|
+
current = failure.cause;
|
|
628
|
+
}
|
|
629
|
+
if (attempt >= 6 || !retryable || invalidated) throw error;
|
|
630
|
+
await setTimeout(5 * 2 ** attempt);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
//#endregion
|
|
634
|
+
//#region src/services/oauth-retention.ts
|
|
635
|
+
var OAUTH_REFRESH_REPLAY_RETENTION_MS = 720 * 60 * 60 * 1e3;
|
|
636
|
+
async function pruneOAuthCredentials(db, options = {}) {
|
|
637
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
638
|
+
const cutoff = (/* @__PURE__ */ new Date(now.getTime() - OAUTH_REFRESH_REPLAY_RETENTION_MS)).toISOString();
|
|
639
|
+
const codes = await UsersOAuthAuthorizationCodeCollection.create({ db });
|
|
640
|
+
const revocations = await UsersOAuthAccessTokenRevocationCollection.create({ db });
|
|
641
|
+
const grants = await UsersOAuthRefreshGrantCollection.create({ db });
|
|
642
|
+
const families = await UsersOAuthRefreshFamilyCollection.create({ db });
|
|
643
|
+
let total = 0;
|
|
644
|
+
for (const table of [codes.tableName, revocations.tableName]) {
|
|
645
|
+
const prune = async (tx) => {
|
|
646
|
+
if (!options.dryRun) return (await tx.query(`DELETE FROM ${table} WHERE expires_at < ? RETURNING id`, now.toISOString())).rows.length;
|
|
647
|
+
const counted = await tx.query(`SELECT COUNT(*) AS total FROM ${table} WHERE expires_at < ?`, now.toISOString());
|
|
648
|
+
return Number(counted.rows[0]?.total ?? 0);
|
|
649
|
+
};
|
|
650
|
+
total += options.dryRun ? await prune(db) : await withOAuthTransaction(db, prune);
|
|
651
|
+
}
|
|
652
|
+
const candidates = await db.query(`SELECT id FROM ${families.tableName} f WHERE EXISTS (SELECT 1 FROM ${grants.tableName} g WHERE g.family_id = CAST(f.id AS TEXT) AND g.expires_at < ?) OR NOT EXISTS (SELECT 1 FROM ${grants.tableName} g WHERE g.family_id = CAST(f.id AS TEXT)) ORDER BY id`, cutoff);
|
|
653
|
+
for (const candidate of candidates.rows) {
|
|
654
|
+
const id = String(candidate.id);
|
|
655
|
+
const prune = async (tx) => {
|
|
656
|
+
if (!options.dryRun) {
|
|
657
|
+
if ((await tx.query(`UPDATE ${families.tableName} SET updated_at = ? WHERE id = ?`, now.toISOString(), id)).rowCount !== 1) return 0;
|
|
658
|
+
}
|
|
659
|
+
const counted = await tx.query(`SELECT COUNT(*) AS total FROM ${grants.tableName} WHERE family_id = ? AND expires_at < ?`, id, cutoff);
|
|
660
|
+
const retained = await tx.query(`SELECT COUNT(*) AS total FROM ${grants.tableName} WHERE family_id = ? AND expires_at >= ?`, id, cutoff);
|
|
661
|
+
const empty = Number(retained.rows[0]?.total ?? 0) === 0;
|
|
662
|
+
if (!options.dryRun) {
|
|
663
|
+
await tx.query(`DELETE FROM ${grants.tableName} WHERE family_id = ? AND expires_at < ?`, id, cutoff);
|
|
664
|
+
if (empty) await tx.query(`DELETE FROM ${families.tableName} WHERE id = ?`, id);
|
|
665
|
+
}
|
|
666
|
+
return Number(counted.rows[0]?.total ?? 0) + Number(empty);
|
|
667
|
+
};
|
|
668
|
+
total += options.dryRun ? await prune(db) : await withOAuthTransaction(db, prune);
|
|
669
|
+
}
|
|
670
|
+
return total;
|
|
671
|
+
}
|
|
672
|
+
//#endregion
|
|
282
673
|
//#region src/retention.ts
|
|
283
674
|
var SESSIONS_RETENTION_TASK = "users-sessions";
|
|
284
675
|
var MAGIC_LINK_RETENTION_TASK = "users-magic-link-tokens";
|
|
@@ -286,7 +677,9 @@ var CLI_AUTH_RETENTION_TASK = "users-cli-auth-requests";
|
|
|
286
677
|
var LOGIN_ATTEMPTS_RETENTION_TASK = "users-login-attempts";
|
|
287
678
|
var LOGIN_AUDIT_RETENTION_TASK = "users-login-audit-events";
|
|
288
679
|
var DEFAULT_LOGIN_AUDIT_RETENTION_DAYS = 90;
|
|
680
|
+
var OAUTH_CREDENTIALS_RETENTION_TASK = "users-oauth-credentials";
|
|
289
681
|
var USER_RETENTION_TASKS = [
|
|
682
|
+
OAUTH_CREDENTIALS_RETENTION_TASK,
|
|
290
683
|
SESSIONS_RETENTION_TASK,
|
|
291
684
|
MAGIC_LINK_RETENTION_TASK,
|
|
292
685
|
CLI_AUTH_RETENTION_TASK,
|
|
@@ -294,6 +687,14 @@ var USER_RETENTION_TASKS = [
|
|
|
294
687
|
LOGIN_AUDIT_RETENTION_TASK
|
|
295
688
|
];
|
|
296
689
|
function registerUserRetentionTasks() {
|
|
690
|
+
registerRetentionTask({
|
|
691
|
+
name: OAUTH_CREDENTIALS_RETENTION_TASK,
|
|
692
|
+
description: "Delete expired OAuth credentials after their replay window",
|
|
693
|
+
run: (db, context) => pruneOAuthCredentials(db, {
|
|
694
|
+
dryRun: context.dryRun,
|
|
695
|
+
now: context.now
|
|
696
|
+
})
|
|
697
|
+
});
|
|
297
698
|
registerRetentionTask({
|
|
298
699
|
name: SESSIONS_RETENTION_TASK,
|
|
299
700
|
description: "Delete expired and revoked sessions",
|
|
@@ -1492,7 +1893,7 @@ var MagicLinkService = class MagicLinkService {
|
|
|
1492
1893
|
* The caller is responsible for emailing the token to the user.
|
|
1493
1894
|
*/
|
|
1494
1895
|
async generate(email) {
|
|
1495
|
-
const { SignJWT } = await import("./chunks/TerminalAuthService-
|
|
1896
|
+
const { SignJWT } = await import("./chunks/TerminalAuthService-DWPeKl3k.js").then((n) => n.R);
|
|
1496
1897
|
const key = await this.getSigningKey();
|
|
1497
1898
|
const nonce = crypto.randomUUID();
|
|
1498
1899
|
const normalizedEmail = normalizeEmail(email);
|
|
@@ -1521,7 +1922,7 @@ var MagicLinkService = class MagicLinkService {
|
|
|
1521
1922
|
* @throws {MagicLinkError} If the token is invalid, expired, or already used
|
|
1522
1923
|
*/
|
|
1523
1924
|
async verify(token) {
|
|
1524
|
-
const { jwtVerify, errors } = await import("./chunks/TerminalAuthService-
|
|
1925
|
+
const { jwtVerify, errors } = await import("./chunks/TerminalAuthService-DWPeKl3k.js").then((n) => n.R);
|
|
1525
1926
|
const key = await this.getSigningKey();
|
|
1526
1927
|
let payload;
|
|
1527
1928
|
try {
|
|
@@ -1555,6 +1956,404 @@ var MagicLinkService = class MagicLinkService {
|
|
|
1555
1956
|
}
|
|
1556
1957
|
};
|
|
1557
1958
|
//#endregion
|
|
1959
|
+
//#region src/services/OAuthAuthorizationStorage.ts
|
|
1960
|
+
var SMRT_OAUTH_GRANT_CLAIM = "smrt_grant_id";
|
|
1961
|
+
function authorizationId(grant) {
|
|
1962
|
+
const id = grant.claims?.[SMRT_OAUTH_GRANT_CLAIM];
|
|
1963
|
+
return typeof id === "string" ? id : null;
|
|
1964
|
+
}
|
|
1965
|
+
function codeGrant(row) {
|
|
1966
|
+
return {
|
|
1967
|
+
id: row.id,
|
|
1968
|
+
codeHash: row.codeHash,
|
|
1969
|
+
clientId: row.clientId,
|
|
1970
|
+
subject: row.subject,
|
|
1971
|
+
...row.tenantId ? { tenantId: row.tenantId } : {},
|
|
1972
|
+
claims: row.getClaims(),
|
|
1973
|
+
redirectUri: row.redirectUri,
|
|
1974
|
+
scopes: row.getScopes(),
|
|
1975
|
+
...row.resource ? { resource: row.resource } : {},
|
|
1976
|
+
codeChallenge: row.codeChallenge,
|
|
1977
|
+
expiresAt: row.expiresAt
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
function refreshGrant(row) {
|
|
1981
|
+
return {
|
|
1982
|
+
id: row.id,
|
|
1983
|
+
tokenHash: row.tokenHash,
|
|
1984
|
+
familyId: row.familyId,
|
|
1985
|
+
clientId: row.clientId,
|
|
1986
|
+
subject: row.subject,
|
|
1987
|
+
...row.tenantId ? { tenantId: row.tenantId } : {},
|
|
1988
|
+
claims: row.getClaims(),
|
|
1989
|
+
scopes: row.getScopes(),
|
|
1990
|
+
...row.resource ? { resource: row.resource } : {},
|
|
1991
|
+
expiresAt: row.expiresAt
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
var SmrtOAuthAuthorizationStorage = class SmrtOAuthAuthorizationStorage {
|
|
1995
|
+
constructor(db, recoverDatabase) {
|
|
1996
|
+
this.db = db;
|
|
1997
|
+
this.recoverDatabase = recoverDatabase;
|
|
1998
|
+
}
|
|
1999
|
+
db;
|
|
2000
|
+
recoverDatabase;
|
|
2001
|
+
recovering = null;
|
|
2002
|
+
async execute(action) {
|
|
2003
|
+
for (let attempt = 0;; attempt++) {
|
|
2004
|
+
const previous = this.db;
|
|
2005
|
+
try {
|
|
2006
|
+
return await action();
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
let cause = error;
|
|
2009
|
+
let invalidated = false;
|
|
2010
|
+
for (let depth = 0; depth < 6 && cause && typeof cause === "object"; depth++) {
|
|
2011
|
+
const failure = cause;
|
|
2012
|
+
invalidated ||= failure.connectionInvalidated === true;
|
|
2013
|
+
cause = failure.cause;
|
|
2014
|
+
}
|
|
2015
|
+
const recover = this.recoverDatabase;
|
|
2016
|
+
if (!invalidated || !recover || attempt >= 6) throw error;
|
|
2017
|
+
if (this.db === previous) {
|
|
2018
|
+
this.recovering ??= (async () => {
|
|
2019
|
+
const db = await recover();
|
|
2020
|
+
if (!db.transaction) throw new Error("OAuth recovery requires transaction support.");
|
|
2021
|
+
this.db = db;
|
|
2022
|
+
})().finally(() => {
|
|
2023
|
+
this.recovering = null;
|
|
2024
|
+
});
|
|
2025
|
+
await this.recovering;
|
|
2026
|
+
}
|
|
2027
|
+
await setTimeout(5 * 2 ** attempt);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
static async create(options) {
|
|
2032
|
+
const rows = await UsersOAuthClientCollection.create(options.recoverDatabase ? {
|
|
2033
|
+
...options,
|
|
2034
|
+
db: await options.recoverDatabase()
|
|
2035
|
+
} : options);
|
|
2036
|
+
if (!rows.db.transaction) throw new Error("OAuth storage requires transaction support.");
|
|
2037
|
+
return new SmrtOAuthAuthorizationStorage(rows.db, options.recoverDatabase);
|
|
2038
|
+
}
|
|
2039
|
+
async lockAuthorization(db, id, now) {
|
|
2040
|
+
if (!id) return true;
|
|
2041
|
+
return (await db.query("UPDATE users_oauth_authorizations SET updated_at = ? WHERE id = ? AND revoked_at IS NULL", now.toISOString(), id)).rowCount === 1;
|
|
2042
|
+
}
|
|
2043
|
+
async revokeAuthorization(db, id, now) {
|
|
2044
|
+
if (!id) return;
|
|
2045
|
+
const time = now.toISOString();
|
|
2046
|
+
await db.query("UPDATE users_oauth_authorizations SET revoked_at = ?, updated_at = ? WHERE id = ?", time, time, id);
|
|
2047
|
+
await db.query("UPDATE users_oauth_authorization_codes SET consumed_at = ? WHERE authorization_id = ? AND consumed_at IS NULL", time, id);
|
|
2048
|
+
await db.query("UPDATE users_oauth_refresh_families SET revoked_at = ? WHERE authorization_id = ?", time, id);
|
|
2049
|
+
await db.query("UPDATE users_oauth_refresh_grants SET revoked_at = ? WHERE authorization_id = ?", time, id);
|
|
2050
|
+
}
|
|
2051
|
+
async getClient(clientId) {
|
|
2052
|
+
return this.execute(async () => {
|
|
2053
|
+
const client = await (await UsersOAuthClientCollection.create({ db: this.db })).findByClientId(clientId);
|
|
2054
|
+
return client ? {
|
|
2055
|
+
id: client.clientId,
|
|
2056
|
+
redirectUris: client.getRedirectUris(),
|
|
2057
|
+
allowedScopes: client.getAllowedScopes(),
|
|
2058
|
+
tokenEndpointAuthMethod: client.tokenEndpointAuthMethod,
|
|
2059
|
+
...client.secretHash ? { secretHash: client.secretHash } : {},
|
|
2060
|
+
...client.getAllowedResources() !== void 0 ? { allowedResources: client.getAllowedResources() } : {},
|
|
2061
|
+
createdAt: client.createdAt
|
|
2062
|
+
} : null;
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
async registerClient(client) {
|
|
2066
|
+
return this.execute(async () => {
|
|
2067
|
+
await (await (await UsersOAuthClientCollection.create({ db: this.db })).create({
|
|
2068
|
+
clientId: client.id,
|
|
2069
|
+
redirectUris: JSON.stringify(client.redirectUris),
|
|
2070
|
+
allowedScopes: JSON.stringify(client.allowedScopes),
|
|
2071
|
+
tokenEndpointAuthMethod: client.tokenEndpointAuthMethod,
|
|
2072
|
+
secretHash: client.secretHash ?? null,
|
|
2073
|
+
allowedResources: JSON.stringify(client.allowedResources ?? null),
|
|
2074
|
+
createdAt: client.createdAt,
|
|
2075
|
+
_insertOnly: true
|
|
2076
|
+
})).save();
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
async createAuthorizationCode(grant) {
|
|
2080
|
+
return this.execute(async () => {
|
|
2081
|
+
await withOAuthTransaction(this.db, async (db) => {
|
|
2082
|
+
if (!await this.lockAuthorization(db, authorizationId(grant), /* @__PURE__ */ new Date())) throw new Error("Authorization is revoked.");
|
|
2083
|
+
await (await (await UsersOAuthAuthorizationCodeCollection.create({ db })).create({
|
|
2084
|
+
id: grant.id,
|
|
2085
|
+
authorizationId: authorizationId(grant),
|
|
2086
|
+
codeHash: grant.codeHash,
|
|
2087
|
+
clientId: grant.clientId,
|
|
2088
|
+
subject: grant.subject,
|
|
2089
|
+
tenantId: grant.tenantId ?? null,
|
|
2090
|
+
claims: JSON.stringify(grant.claims ?? {}),
|
|
2091
|
+
redirectUri: grant.redirectUri,
|
|
2092
|
+
scopes: JSON.stringify(grant.scopes),
|
|
2093
|
+
resource: grant.resource ?? null,
|
|
2094
|
+
codeChallenge: grant.codeChallenge,
|
|
2095
|
+
expiresAt: grant.expiresAt,
|
|
2096
|
+
consumedAt: null,
|
|
2097
|
+
_insertOnly: true
|
|
2098
|
+
})).save();
|
|
2099
|
+
});
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
async consumeAuthorizationCode(input) {
|
|
2103
|
+
return this.execute(async () => {
|
|
2104
|
+
const row = await (await UsersOAuthAuthorizationCodeCollection.create({ db: this.db })).get(input.id);
|
|
2105
|
+
if (!row || row.codeHash !== input.codeHash || row.clientId !== input.clientId || row.redirectUri !== input.redirectUri || (row.resource ?? void 0) !== input.resource) return { status: "invalid" };
|
|
2106
|
+
return withOAuthTransaction(this.db, async (db) => {
|
|
2107
|
+
if (!await this.lockAuthorization(db, row.authorizationId, input.now)) return { status: "invalid" };
|
|
2108
|
+
const rows = await UsersOAuthAuthorizationCodeCollection.create({ db });
|
|
2109
|
+
if (await rows.consume(input.id, input.codeHash, input.clientId, input.redirectUri, input.resource ?? null, input.now)) return {
|
|
2110
|
+
status: "consumed",
|
|
2111
|
+
grant: codeGrant(row)
|
|
2112
|
+
};
|
|
2113
|
+
const current = await rows.get(input.id);
|
|
2114
|
+
return { status: current?.consumedAt ? "replayed" : current && current.expiresAt <= input.now ? "expired" : "invalid" };
|
|
2115
|
+
});
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
async createRefreshGrant(grant) {
|
|
2119
|
+
return this.execute(async () => {
|
|
2120
|
+
await withOAuthTransaction(this.db, async (db) => {
|
|
2121
|
+
if (!await this.lockAuthorization(db, authorizationId(grant), /* @__PURE__ */ new Date())) throw new Error("Authorization is revoked.");
|
|
2122
|
+
await (await (await UsersOAuthRefreshFamilyCollection.create({ db })).create({
|
|
2123
|
+
id: grant.familyId,
|
|
2124
|
+
authorizationId: authorizationId(grant),
|
|
2125
|
+
revokedAt: null,
|
|
2126
|
+
_insertOnly: true
|
|
2127
|
+
})).save();
|
|
2128
|
+
await this.saveRefresh(await UsersOAuthRefreshGrantCollection.create({ db }), grant);
|
|
2129
|
+
});
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
async rotateRefreshGrant(input) {
|
|
2133
|
+
return this.execute(async () => {
|
|
2134
|
+
const initial = await (await UsersOAuthRefreshGrantCollection.create({ db: this.db })).get(input.id);
|
|
2135
|
+
if (!initial || initial.tokenHash !== input.tokenHash || initial.clientId !== input.clientId || (initial.resource ?? void 0) !== input.resource) return { status: "invalid" };
|
|
2136
|
+
return withOAuthTransaction(this.db, async (db) => {
|
|
2137
|
+
if (!await this.lockAuthorization(db, initial.authorizationId, input.now)) return { status: "revoked" };
|
|
2138
|
+
const families = await UsersOAuthRefreshFamilyCollection.create({ db });
|
|
2139
|
+
if (!await families.lock(initial.familyId, input.now)) return { status: "invalid" };
|
|
2140
|
+
const family = await families.get(initial.familyId);
|
|
2141
|
+
const rows = await UsersOAuthRefreshGrantCollection.create({ db });
|
|
2142
|
+
const row = await rows.get(input.id);
|
|
2143
|
+
if (!row) return { status: "invalid" };
|
|
2144
|
+
if (family?.revokedAt || row.revokedAt) return { status: "revoked" };
|
|
2145
|
+
if (row.consumedAt) {
|
|
2146
|
+
await families.revoke(row.familyId, input.now);
|
|
2147
|
+
await rows.revokeFamily(row.familyId, input.now);
|
|
2148
|
+
await this.revokeAuthorization(db, row.authorizationId, input.now);
|
|
2149
|
+
return { status: "replayed" };
|
|
2150
|
+
}
|
|
2151
|
+
if (row.expiresAt <= input.now) return { status: "expired" };
|
|
2152
|
+
if (!await rows.consume(input.id, input.tokenHash, input.clientId, input.resource ?? null, input.now)) return { status: "invalid" };
|
|
2153
|
+
const grant = refreshGrant(row);
|
|
2154
|
+
await this.saveRefresh(rows, {
|
|
2155
|
+
...grant,
|
|
2156
|
+
...input.replacement
|
|
2157
|
+
});
|
|
2158
|
+
return {
|
|
2159
|
+
status: "rotated",
|
|
2160
|
+
grant
|
|
2161
|
+
};
|
|
2162
|
+
});
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
/** Persist a narrowed replacement before the SDK exposes it to a caller. */
|
|
2166
|
+
async narrowRefreshGrant(input) {
|
|
2167
|
+
return this.execute(async () => {
|
|
2168
|
+
const initial = await (await UsersOAuthRefreshGrantCollection.create({ db: this.db })).get(input.id);
|
|
2169
|
+
if (!initial || initial.tokenHash !== input.tokenHash) throw new Error("Invalid refresh grant.");
|
|
2170
|
+
await withOAuthTransaction(this.db, async (db) => {
|
|
2171
|
+
const now = /* @__PURE__ */ new Date();
|
|
2172
|
+
if (!await this.lockAuthorization(db, initial.authorizationId, now)) throw new Error("Authorization is revoked.");
|
|
2173
|
+
const families = await UsersOAuthRefreshFamilyCollection.create({ db });
|
|
2174
|
+
if (!await families.lock(initial.familyId, now)) throw new Error("Invalid refresh family.");
|
|
2175
|
+
const family = await families.get(initial.familyId);
|
|
2176
|
+
const row = await (await UsersOAuthRefreshGrantCollection.create({ db })).get(input.id);
|
|
2177
|
+
if (!row || row.tokenHash !== input.tokenHash || family?.revokedAt || row.revokedAt || row.consumedAt || row.expiresAt <= now || input.scopes.some((scope) => !row.getScopes().includes(scope))) throw new Error("Invalid refresh scope narrowing.");
|
|
2178
|
+
await db.query("UPDATE users_oauth_refresh_grants SET scopes = ?, claims = ?, updated_at = ? WHERE id = ?", JSON.stringify(input.scopes), JSON.stringify(input.claims ?? {}), now.toISOString(), input.id);
|
|
2179
|
+
});
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
async revokeRefreshGrant(input) {
|
|
2183
|
+
return this.execute(async () => {
|
|
2184
|
+
const row = await (await UsersOAuthRefreshGrantCollection.create({ db: this.db })).get(input.id);
|
|
2185
|
+
if (!row || row.tokenHash !== input.tokenHash) return;
|
|
2186
|
+
await withOAuthTransaction(this.db, async (db) => {
|
|
2187
|
+
await this.lockAuthorization(db, row.authorizationId, input.now);
|
|
2188
|
+
const families = await UsersOAuthRefreshFamilyCollection.create({ db });
|
|
2189
|
+
await families.lock(row.familyId, input.now);
|
|
2190
|
+
await families.revoke(row.familyId, input.now);
|
|
2191
|
+
await this.revokeAuthorization(db, row.authorizationId, input.now);
|
|
2192
|
+
await (await UsersOAuthRefreshGrantCollection.create({ db })).revokeFamily(row.familyId, input.now);
|
|
2193
|
+
});
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
async revokeAccessToken(input) {
|
|
2197
|
+
return this.execute(async () => {
|
|
2198
|
+
await withOAuthTransaction(this.db, async (db) => {
|
|
2199
|
+
await db.query("INSERT INTO users_oauth_access_token_revocations (id, slug, jti, expires_at) VALUES (?, ?, ?, ?) ON CONFLICT (jti) DO NOTHING", randomUUID(), randomUUID(), input.jti, input.expiresAt.toISOString());
|
|
2200
|
+
});
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
async isAccessTokenRevoked(jti, now) {
|
|
2204
|
+
return this.execute(async () => {
|
|
2205
|
+
const row = await (await UsersOAuthAccessTokenRevocationCollection.create({ db: this.db })).findByJti(jti);
|
|
2206
|
+
return !!row && row.expiresAt > now;
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
async saveRefresh(rows, grant) {
|
|
2210
|
+
await (await rows.create({
|
|
2211
|
+
id: grant.id,
|
|
2212
|
+
authorizationId: authorizationId(grant),
|
|
2213
|
+
tokenHash: grant.tokenHash,
|
|
2214
|
+
familyId: grant.familyId,
|
|
2215
|
+
clientId: grant.clientId,
|
|
2216
|
+
subject: grant.subject,
|
|
2217
|
+
tenantId: grant.tenantId ?? null,
|
|
2218
|
+
claims: JSON.stringify(grant.claims ?? {}),
|
|
2219
|
+
scopes: JSON.stringify(grant.scopes),
|
|
2220
|
+
resource: grant.resource ?? null,
|
|
2221
|
+
expiresAt: grant.expiresAt,
|
|
2222
|
+
consumedAt: null,
|
|
2223
|
+
revokedAt: null,
|
|
2224
|
+
_insertOnly: true
|
|
2225
|
+
})).save();
|
|
2226
|
+
}
|
|
2227
|
+
};
|
|
2228
|
+
//#endregion
|
|
2229
|
+
//#region src/services/OAuthAuthorizationService.ts
|
|
2230
|
+
var SmrtOAuthAuthorizationService = class SmrtOAuthAuthorizationService {
|
|
2231
|
+
constructor(options, sessions, grants) {
|
|
2232
|
+
this.options = options;
|
|
2233
|
+
this.sessions = sessions;
|
|
2234
|
+
this.grants = grants;
|
|
2235
|
+
}
|
|
2236
|
+
options;
|
|
2237
|
+
sessions;
|
|
2238
|
+
grants;
|
|
2239
|
+
static async create(options) {
|
|
2240
|
+
const sessions = await SessionService.create({
|
|
2241
|
+
...options,
|
|
2242
|
+
autoExtend: false
|
|
2243
|
+
});
|
|
2244
|
+
return new SmrtOAuthAuthorizationService(options, sessions, await UsersOAuthAuthorizationCollection.create({ db: sessions.getDatabase() }));
|
|
2245
|
+
}
|
|
2246
|
+
/** Suitable for createAuthorizationServer({ identity: service.identity }). */
|
|
2247
|
+
identity = {
|
|
2248
|
+
refreshConsent: (context, grant) => this.revalidateConsent(context, grant),
|
|
2249
|
+
revalidateConsent: (context, grant) => this.revalidateConsent(context, grant)
|
|
2250
|
+
};
|
|
2251
|
+
async liveSession(sessionId) {
|
|
2252
|
+
const context = await this.sessions.loadSessionContext(sessionId);
|
|
2253
|
+
if (!context) return null;
|
|
2254
|
+
if (context.tenantId) {
|
|
2255
|
+
if (!context.tenantAuthorization?.membershipId) return null;
|
|
2256
|
+
if (!(await (await TenantCollection.create({ db: this.sessions.getDatabase() })).get(context.tenantId))?.isActive()) return null;
|
|
2257
|
+
}
|
|
2258
|
+
return context;
|
|
2259
|
+
}
|
|
2260
|
+
permits(context, scopes) {
|
|
2261
|
+
return scopes.every((scope) => Object.hasOwn(this.options.scopePermissions, scope) && this.options.scopePermissions[scope].every((permission) => context.permissions.includes(permission)));
|
|
2262
|
+
}
|
|
2263
|
+
/** Call only after an explicit user consent action; the session is loaded again. */
|
|
2264
|
+
async approve(server, request, sessionId) {
|
|
2265
|
+
const context = await this.liveSession(sessionId);
|
|
2266
|
+
if (!context || !this.permits(context, request.scopes)) throw new OAuthServerError("access_denied", "Authorization denied.", 403);
|
|
2267
|
+
const row = await this.grants.create({
|
|
2268
|
+
subject: context.user.id,
|
|
2269
|
+
sessionId,
|
|
2270
|
+
clientId: request.clientId,
|
|
2271
|
+
tenantId: context.tenantId,
|
|
2272
|
+
scopes: JSON.stringify(request.scopes),
|
|
2273
|
+
permissionCeiling: JSON.stringify(context.permissions),
|
|
2274
|
+
resource: request.resource ?? null,
|
|
2275
|
+
revokedAt: null,
|
|
2276
|
+
_insertOnly: true
|
|
2277
|
+
});
|
|
2278
|
+
await row.save();
|
|
2279
|
+
try {
|
|
2280
|
+
return await server.approve(request, {
|
|
2281
|
+
subject: context.user.id,
|
|
2282
|
+
...context.tenantId ? { tenantId: context.tenantId } : {},
|
|
2283
|
+
claims: { [SMRT_OAUTH_GRANT_CLAIM]: row.id }
|
|
2284
|
+
});
|
|
2285
|
+
} catch (error) {
|
|
2286
|
+
await this.revokeOwned(context.user.id, row.id);
|
|
2287
|
+
throw error;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
async resolve(grantId, subject, tenantId, clientId, scopes, resource) {
|
|
2291
|
+
if (typeof grantId !== "string" || typeof subject !== "string" || typeof clientId !== "string") return null;
|
|
2292
|
+
const grant = await this.grants.get(grantId);
|
|
2293
|
+
if (!grant || grant.revokedAt || grant.subject !== subject || grant.clientId !== clientId || (grant.tenantId ?? void 0) !== (tenantId ?? void 0) || grant.resource && grant.resource !== resource || scopes.some((scope) => !grant.getScopes().includes(scope))) return null;
|
|
2294
|
+
const context = await this.liveSession(grant.sessionId);
|
|
2295
|
+
if (!context || context.user.id !== grant.subject || context.tenantId !== grant.tenantId || !this.permits(context, scopes) || scopes.some((scope) => this.options.scopePermissions[scope]?.some((permission) => !grant.getPermissionCeiling().includes(permission)))) return null;
|
|
2296
|
+
const current = await this.grants.get(grantId);
|
|
2297
|
+
if (!current || current.revokedAt) return null;
|
|
2298
|
+
const allowed = new Set(scopes.flatMap((scope) => this.options.scopePermissions[scope] ?? []));
|
|
2299
|
+
const permissions = context.permissions.filter((permission) => allowed.has(permission));
|
|
2300
|
+
return {
|
|
2301
|
+
...context,
|
|
2302
|
+
permissions,
|
|
2303
|
+
permissionCeiling: permissions
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
async revalidateConsent(context, grant) {
|
|
2307
|
+
return await this.resolve(context.claims?.["smrt_grant_id"], grant.subject, grant.tenantId, grant.clientId, grant.scopes, grant.resource) && context.subject === grant.subject && context.tenantId === grant.tenantId ? context : null;
|
|
2308
|
+
}
|
|
2309
|
+
/** Call after SDK signature/audience/revocation verification, never on decoded-only JWTs. */
|
|
2310
|
+
async validateAccessTokenClaims(payload) {
|
|
2311
|
+
if (typeof payload.scope !== "string") return null;
|
|
2312
|
+
return this.resolve(payload[SMRT_OAUTH_GRANT_CLAIM], payload.sub, payload.tenant_id, payload.client_id, payload.scope.split(" ").filter(Boolean), payload.aud);
|
|
2313
|
+
}
|
|
2314
|
+
/** Lists only the current authenticated user's consent decisions. */
|
|
2315
|
+
async listGrants(sessionId) {
|
|
2316
|
+
const context = await this.liveSession(sessionId);
|
|
2317
|
+
if (!context) throw new OAuthServerError("access_denied", "Sign in required.", 401);
|
|
2318
|
+
const result = await this.sessions.getDatabase().query("SELECT id FROM users_oauth_authorizations WHERE subject = ? ORDER BY created_at DESC", context.user.id);
|
|
2319
|
+
const grants = [];
|
|
2320
|
+
for (const row of result.rows) {
|
|
2321
|
+
const grant = await this.grants.get(String(row.id));
|
|
2322
|
+
if (grant) grants.push(this.summary(grant));
|
|
2323
|
+
}
|
|
2324
|
+
return grants;
|
|
2325
|
+
}
|
|
2326
|
+
/** Revocation is idempotent and confined to the authenticated subject. */
|
|
2327
|
+
async revokeGrant(sessionId, grantId) {
|
|
2328
|
+
const context = await this.liveSession(sessionId);
|
|
2329
|
+
if (!context) throw new OAuthServerError("access_denied", "Sign in required.", 401);
|
|
2330
|
+
return this.revokeOwned(context.user.id, grantId);
|
|
2331
|
+
}
|
|
2332
|
+
async revokeOwned(subject, grantId) {
|
|
2333
|
+
const db = this.sessions.getDatabase();
|
|
2334
|
+
if (!db.transaction) throw new Error("OAuth revocation requires transaction support.");
|
|
2335
|
+
return withOAuthTransaction(db, async (db2) => {
|
|
2336
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2337
|
+
if ((await db2.query("UPDATE users_oauth_authorizations SET revoked_at = ?, updated_at = ? WHERE id = ? AND subject = ?", now, now, grantId, subject)).rowCount !== 1) return false;
|
|
2338
|
+
await db2.query("UPDATE users_oauth_authorization_codes SET consumed_at = ? WHERE authorization_id = ? AND consumed_at IS NULL", now, grantId);
|
|
2339
|
+
await db2.query("UPDATE users_oauth_refresh_families SET revoked_at = ? WHERE authorization_id = ?", now, grantId);
|
|
2340
|
+
await db2.query("UPDATE users_oauth_refresh_grants SET revoked_at = ? WHERE authorization_id = ?", now, grantId);
|
|
2341
|
+
return true;
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
summary(grant) {
|
|
2345
|
+
return {
|
|
2346
|
+
id: grant.id,
|
|
2347
|
+
clientId: grant.clientId,
|
|
2348
|
+
tenantId: grant.tenantId,
|
|
2349
|
+
scopes: grant.getScopes(),
|
|
2350
|
+
resource: grant.resource,
|
|
2351
|
+
createdAt: grant.created_at ?? null,
|
|
2352
|
+
revokedAt: grant.revokedAt
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
};
|
|
2356
|
+
//#endregion
|
|
1558
2357
|
//#region src/services/PostgresPermissionPolicies.ts
|
|
1559
2358
|
function toSnakeCase(value) {
|
|
1560
2359
|
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
|
|
@@ -2119,6 +2918,6 @@ var TenantService = class TenantService {
|
|
|
2119
2918
|
//#region src/index.ts
|
|
2120
2919
|
registerUserRetentionTasks();
|
|
2121
2920
|
//#endregion
|
|
2122
|
-
export { ACCESS_REQUEST_CAPABILITIES, ANCESTOR_READ_ACTION, AccessRequest, AccessRequestCollection, AccessRequestError, AccessRequestService, AccessRequestStatus, CLI_AUTH_RETENTION_TASK, UsersCliAuthRequest as CliAuthRequest, UsersCliAuthRequest, UsersCliAuthRequestCollection as CliAuthRequestCollection, UsersCliAuthRequestCollection, DEFAULT_ANCESTOR_READ_MAX_DEPTH, DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, DEFAULT_CLI_SESSION_TTL_SECONDS, DEFAULT_DEVICE_PERSON_IDLE_SECONDS, DEFAULT_LOGIN_ATTEMPT_WINDOW_SECONDS, DEFAULT_LOGIN_AUDIT_RETENTION_DAYS, DEFAULT_LOGIN_LOCKOUT_BASE_SECONDS, DEFAULT_LOGIN_LOCKOUT_FACTOR, DEFAULT_LOGIN_LOCKOUT_MAX_SECONDS, DEFAULT_LOGIN_MAX_ATTEMPTS, DEFAULT_PASSWORD_DENYLIST, DEFAULT_PASSWORD_MANAGE_PERMISSION, DEFAULT_PASSWORD_MAX_LENGTH, DEFAULT_PASSWORD_MIN_LENGTH, DEFAULT_PIN_MANAGE_PERMISSION, DEFAULT_ROLES, DEFAULT_ROLE_PERMISSION_PATTERNS, DEFAULT_ROLE_SLUGS, DEFAULT_SESSION_TTL, DEFAULT_TENANT_POLICY, DEFAULT_TOKEN_EXPIRY_SECONDS, DeviceCredentialError, DeviceCredentialForbiddenError, DeviceCredentialPolicyError, DeviceCredentialService, DurableLoginAuditSink, Group, GroupCollection, GroupMember, GroupMemberCollection, GroupRole, GroupRoleCollection, InvalidCredentialsError, LEGACY_USER_PROFILE_BACKFILL_NAME, LOGIN_ATTEMPTS_RETENTION_TASK, LOGIN_AUDIT_RETENTION_TASK, LegacyUserProfileBackfillError, LoginAttemptLimiter, LoginRateLimitError, MAGIC_LINK_RETENTION_TASK, MAX_RESOURCE_GRANT_DELEGATION_DEPTH, MAX_TENANT_HIERARCHY_DEPTH, MagicLinkError, MagicLinkService, UsersMagicLinkToken as MagicLinkToken, UsersMagicLinkToken, UsersMagicLinkTokenCollection as MagicLinkTokenCollection, UsersMagicLinkTokenCollection, Membership, MembershipCollection, MembershipOverride, MembershipOverrideCollection, MembershipStatus, MobileAuthError, MobileAuthService, OidcLoginError, OidcLoginService, OidcProvisioningError, OperationPermissionError, OverrideEffect, PASSWORD_LOGIN_KIND, PASSWORD_MAX_LENGTH_CEILING, PASSWORD_PERMISSION_DEFINITIONS, PIN_LOGIN_KIND, PIN_PERMISSION_DEFINITIONS, PasswordCredentialError, PasswordCredentialForbiddenError, PasswordCredentialService, PasswordPolicyError, Permission, PermissionCatalogService, PermissionCollection, PermissionResolver, PinVerifier, ResourceGrant, ResourceGrantCollection, ResourceGrantService, Role, RoleCollection, RolePermission, RolePermissionCollection, SESSIONS_RETENTION_TASK, SESSION_DATA_KEYS, Session, SessionCollection, SessionService, SessionStatus, TERMINAL_APPROVE_LOGIN_KIND, Tenant, TenantCollection, TenantHierarchyError, TenantHierarchyMaterializationError, TenantIntegration, TenantIntegrationCollection, TenantPermissionEffect, TenantPermissionOverride, TenantPermissionOverrideCollection, TenantService, TenantStatus, TerminalAuthError, TerminalAuthRateLimitError, TerminalAuthService, USER_RETENTION_TASKS, User, UserCollection, UserEmailKeyBackfillError, UserStatus, UsersCliAuthApproveLimit, UsersCliAuthApproveLimitCollection, UsersLoginAttempt, UsersLoginAttemptCollection, UsersLoginAuditEvent, UsersLoginAuditEventCollection, UsersPasswordCredential, UsersPasswordCredentialCollection, UsersPinCredential, UsersPinCredentialCollection, UsersRolesAndPermissionsRecipe, UsersSignInRecipe, applyPostgresPermissionPolicies, assertOperationPermission, backfillLegacyUserProfiles, backfillUserEmailKeys, checkOperationPermission, checkResourceOperationPermission, decodeOidcTransaction, deduplicateRolePermissions, deriveOperationPermissionCollectionName, deriveOperationPermissionSlug, encodeOidcTransaction, ensurePasswordPermissionsRegistered, ensurePinPermissionsRegistered, generatePostgresPermissionSql, generateSessionId, getConfiguredAncestorReadPolicy, getCurrentSessionPermissionContext, getRequestScopedDatabase, getUsersOidcConfig, hasOperationPermission, isAncestorReadableSlug, materializeTenantHierarchy, normalizeAncestorReadPolicy, normalizeOperationPermissionAction, planTenantHierarchy, readMobileBearerToken, registerPermissionDefinitions, registerUserRetentionTasks, resolveOidcProviderConfig, syncPermissionCatalog, unregisterUserRetentionTasks, validateMobileRedirectUri, withPrincipalPermissionContext, withSessionPermissionContext };
|
|
2921
|
+
export { ACCESS_REQUEST_CAPABILITIES, ANCESTOR_READ_ACTION, AccessRequest, AccessRequestCollection, AccessRequestError, AccessRequestService, AccessRequestStatus, CLI_AUTH_RETENTION_TASK, UsersCliAuthRequest as CliAuthRequest, UsersCliAuthRequest, UsersCliAuthRequestCollection as CliAuthRequestCollection, UsersCliAuthRequestCollection, DEFAULT_ANCESTOR_READ_MAX_DEPTH, DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, DEFAULT_CLI_SESSION_TTL_SECONDS, DEFAULT_DEVICE_PERSON_IDLE_SECONDS, DEFAULT_LOGIN_ATTEMPT_WINDOW_SECONDS, DEFAULT_LOGIN_AUDIT_RETENTION_DAYS, DEFAULT_LOGIN_LOCKOUT_BASE_SECONDS, DEFAULT_LOGIN_LOCKOUT_FACTOR, DEFAULT_LOGIN_LOCKOUT_MAX_SECONDS, DEFAULT_LOGIN_MAX_ATTEMPTS, DEFAULT_PASSWORD_DENYLIST, DEFAULT_PASSWORD_MANAGE_PERMISSION, DEFAULT_PASSWORD_MAX_LENGTH, DEFAULT_PASSWORD_MIN_LENGTH, DEFAULT_PIN_MANAGE_PERMISSION, DEFAULT_ROLES, DEFAULT_ROLE_PERMISSION_PATTERNS, DEFAULT_ROLE_SLUGS, DEFAULT_SESSION_TTL, DEFAULT_TENANT_POLICY, DEFAULT_TOKEN_EXPIRY_SECONDS, DeviceCredentialError, DeviceCredentialForbiddenError, DeviceCredentialPolicyError, DeviceCredentialService, DurableLoginAuditSink, Group, GroupCollection, GroupMember, GroupMemberCollection, GroupRole, GroupRoleCollection, InvalidCredentialsError, LEGACY_USER_PROFILE_BACKFILL_NAME, LOGIN_ATTEMPTS_RETENTION_TASK, LOGIN_AUDIT_RETENTION_TASK, LegacyUserProfileBackfillError, LoginAttemptLimiter, LoginRateLimitError, MAGIC_LINK_RETENTION_TASK, MAX_RESOURCE_GRANT_DELEGATION_DEPTH, MAX_TENANT_HIERARCHY_DEPTH, MagicLinkError, MagicLinkService, UsersMagicLinkToken as MagicLinkToken, UsersMagicLinkToken, UsersMagicLinkTokenCollection as MagicLinkTokenCollection, UsersMagicLinkTokenCollection, Membership, MembershipCollection, MembershipOverride, MembershipOverrideCollection, MembershipStatus, MobileAuthError, MobileAuthService, OAUTH_CREDENTIALS_RETENTION_TASK, OidcLoginError, OidcLoginService, OidcProvisioningError, OperationPermissionError, OverrideEffect, PASSWORD_LOGIN_KIND, PASSWORD_MAX_LENGTH_CEILING, PASSWORD_PERMISSION_DEFINITIONS, PIN_LOGIN_KIND, PIN_PERMISSION_DEFINITIONS, PasswordCredentialError, PasswordCredentialForbiddenError, PasswordCredentialService, PasswordPolicyError, Permission, PermissionCatalogService, PermissionCollection, PermissionResolver, PinVerifier, ResourceGrant, ResourceGrantCollection, ResourceGrantService, Role, RoleCollection, RolePermission, RolePermissionCollection, SESSIONS_RETENTION_TASK, SESSION_DATA_KEYS, SMRT_OAUTH_GRANT_CLAIM, Session, SessionCollection, SessionService, SessionStatus, SmrtOAuthAuthorizationService, SmrtOAuthAuthorizationStorage, TERMINAL_APPROVE_LOGIN_KIND, Tenant, TenantCollection, TenantHierarchyError, TenantHierarchyMaterializationError, TenantIntegration, TenantIntegrationCollection, TenantPermissionEffect, TenantPermissionOverride, TenantPermissionOverrideCollection, TenantService, TenantStatus, TerminalAuthError, TerminalAuthRateLimitError, TerminalAuthService, USER_RETENTION_TASKS, User, UserCollection, UserEmailKeyBackfillError, UserStatus, UsersCliAuthApproveLimit, UsersCliAuthApproveLimitCollection, UsersLoginAttempt, UsersLoginAttemptCollection, UsersLoginAuditEvent, UsersLoginAuditEventCollection, UsersOAuthAccessTokenRevocation, UsersOAuthAccessTokenRevocationCollection, UsersOAuthAuthorization, UsersOAuthAuthorizationCode, UsersOAuthAuthorizationCodeCollection, UsersOAuthAuthorizationCollection, UsersOAuthClient, UsersOAuthClientCollection, UsersOAuthRefreshFamily, UsersOAuthRefreshFamilyCollection, UsersOAuthRefreshGrant, UsersOAuthRefreshGrantCollection, UsersPasswordCredential, UsersPasswordCredentialCollection, UsersPinCredential, UsersPinCredentialCollection, UsersRolesAndPermissionsRecipe, UsersSignInRecipe, applyPostgresPermissionPolicies, assertOperationPermission, backfillLegacyUserProfiles, backfillUserEmailKeys, checkOperationPermission, checkResourceOperationPermission, decodeOidcTransaction, deduplicateRolePermissions, deriveOperationPermissionCollectionName, deriveOperationPermissionSlug, encodeOidcTransaction, ensurePasswordPermissionsRegistered, ensurePinPermissionsRegistered, generatePostgresPermissionSql, generateSessionId, getConfiguredAncestorReadPolicy, getCurrentSessionPermissionContext, getRequestScopedDatabase, getUsersOidcConfig, hasOperationPermission, isAncestorReadableSlug, materializeTenantHierarchy, normalizeAncestorReadPolicy, normalizeOperationPermissionAction, planTenantHierarchy, readMobileBearerToken, registerPermissionDefinitions, registerUserRetentionTasks, resolveOidcProviderConfig, syncPermissionCatalog, unregisterUserRetentionTasks, validateMobileRedirectUri, withPrincipalPermissionContext, withSessionPermissionContext };
|
|
2123
2922
|
|
|
2124
2923
|
//# sourceMappingURL=index.js.map
|