@better-auth/infra 0.4.0 → 0.4.2
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 +18 -0
- package/README.md +4 -0
- package/dist/client.d.mts +1 -2
- package/dist/client.mjs +9 -7
- package/dist/{constants-CcJHk5SE.mjs → constants-CDhjfr8S.mjs} +1 -1
- package/dist/{crypto-CAdWFWEz.mjs → crypto-D3yZH0Cy.mjs} +21 -6
- package/dist/email.mjs +1 -1
- package/dist/{identify-client-options-RI-JiXQx.d.mts → identify-client-options-CgijjwVT.d.mts} +1 -2
- package/dist/index.d.mts +19 -4
- package/dist/index.mjs +987 -568
- package/dist/native.d.mts +1 -2
- package/dist/native.mjs +7 -6
- package/dist/{pow-retry-DZhZiYd_.mjs → pow-retry-jWGI__oh.mjs} +3 -2
- package/dist/{types-B673lCib.d.mts → types-Ddk4r5x9.d.mts} +0 -1
- package/package.json +6 -4
- package/dist/saml-policy-BTVLoTyS.mjs +0 -12
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as INFRA_USER_AGENT, n as INFRA_API_URL, o as PLUGIN_VERSION, r as INFRA_KV_URL } from "./constants-
|
|
2
|
-
import { a as createAPI, n as hash$1, o as createKV, r as hmacSha256Hex } from "./crypto-
|
|
1
|
+
import { i as INFRA_USER_AGENT, n as INFRA_API_URL, o as PLUGIN_VERSION, r as INFRA_KV_URL } from "./constants-CDhjfr8S.mjs";
|
|
2
|
+
import { a as createAPI, n as hash$1, o as createKV, r as hmacSha256Hex } from "./crypto-D3yZH0Cy.mjs";
|
|
3
3
|
import { EMAIL_TEMPLATES, createEmailSender, sendBulkEmails, sendEmail } from "./email.mjs";
|
|
4
4
|
import { getCurrentAdapter, getCurrentAuthContext, getCurrentDBAdapterAsyncLocalStorage, runWithTransaction } from "@better-auth/core/context";
|
|
5
5
|
import { APIError, generateId, getAuthTables, logger } from "better-auth";
|
|
@@ -7,8 +7,11 @@ import { env } from "@better-auth/core/env";
|
|
|
7
7
|
import { APIError as APIError$1, createAuthEndpoint, createAuthMiddleware, requestPasswordReset, sendVerificationEmailFn, sessionMiddleware } from "better-auth/api";
|
|
8
8
|
import { deleteSessionCookie, setSessionCookie } from "better-auth/cookies";
|
|
9
9
|
import { createFetch } from "@better-fetch/fetch";
|
|
10
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
10
11
|
import { isValidPhoneNumber, parsePhoneNumberFromString } from "libphonenumber-js";
|
|
11
|
-
import z
|
|
12
|
+
import z, { z as z$1 } from "zod";
|
|
13
|
+
import { DOMParser } from "@xmldom/xmldom";
|
|
14
|
+
import { base64Url } from "@better-auth/utils/base64";
|
|
12
15
|
import { createLocalJWKSet, jwtVerify } from "jose";
|
|
13
16
|
import { generateRandomString, symmetricEncrypt } from "better-auth/crypto";
|
|
14
17
|
import { createOTP } from "@better-auth/utils/otp";
|
|
@@ -313,7 +316,8 @@ function tryDecode(value) {
|
|
|
313
316
|
const stripQuery = (value) => value.split("?")[0] || value;
|
|
314
317
|
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
315
318
|
const routeToRegex = (route) => {
|
|
316
|
-
const
|
|
319
|
+
const normalized = stripQuery(route);
|
|
320
|
+
const pattern = escapeRegex(normalized).replace(/\/:([^/]+)/g, "/[^/]+");
|
|
317
321
|
return new RegExp(`^${pattern}(?:$|[/?])`);
|
|
318
322
|
};
|
|
319
323
|
const matchesAnyRoute = (path, routes) => {
|
|
@@ -373,6 +377,33 @@ function createLocalAccountIssuer(providerId) {
|
|
|
373
377
|
function createOAuthAccountIssuer(providerId) {
|
|
374
378
|
return `local:oauth:${encodeURIComponent(providerId)}`;
|
|
375
379
|
}
|
|
380
|
+
/** Configured strategy when set; `null` when omitted (pre-option or default). */
|
|
381
|
+
function getConfiguredAccountIdentityStrategy(account) {
|
|
382
|
+
const strategy = account?.identityStrategy;
|
|
383
|
+
if (strategy === "provider-id" || strategy === "issuer") return strategy;
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Effective strategy for account-key derivation.
|
|
388
|
+
* Matches better-auth: omit/`"issuer"` → issuer-scoped; `"provider-id"` → provider-scoped.
|
|
389
|
+
*/
|
|
390
|
+
function resolveAccountIdentityStrategy(account) {
|
|
391
|
+
return getConfiguredAccountIdentityStrategy(account) ?? "issuer";
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Resolves the OAuth account issuer the same way better-auth does under
|
|
395
|
+
* `account.identityStrategy`, including provider-scoped compatibility mode.
|
|
396
|
+
*/
|
|
397
|
+
async function resolveOAuthAccountIssuer(params) {
|
|
398
|
+
if (params.identityStrategy === "provider-id") return createOAuthAccountIssuer(params.providerId);
|
|
399
|
+
const { accountIssuer } = params;
|
|
400
|
+
if (typeof accountIssuer === "function") {
|
|
401
|
+
const issuer = await accountIssuer(params.context);
|
|
402
|
+
return typeof issuer === "string" && issuer.trim().length > 0 ? issuer : void 0;
|
|
403
|
+
}
|
|
404
|
+
if (typeof accountIssuer === "string" && accountIssuer.trim().length > 0) return accountIssuer;
|
|
405
|
+
return createOAuthAccountIssuer(params.providerId);
|
|
406
|
+
}
|
|
376
407
|
/** True when the installed better-auth build scopes accounts by issuer. */
|
|
377
408
|
function supportsIssuerScopedAccounts(adapter) {
|
|
378
409
|
return typeof adapter.findAccountByKey === "function";
|
|
@@ -407,7 +438,7 @@ function normalizeProviderSubject(subject) {
|
|
|
407
438
|
if (!subject || subject === "undefined" || subject === "null") return;
|
|
408
439
|
return subject;
|
|
409
440
|
}
|
|
410
|
-
async function resolveAccountKey(provider, tokens, profile) {
|
|
441
|
+
async function resolveAccountKey(provider, tokens, profile, identityStrategy) {
|
|
411
442
|
const keyedProvider = provider;
|
|
412
443
|
const context = {
|
|
413
444
|
tokens,
|
|
@@ -423,7 +454,12 @@ async function resolveAccountKey(provider, tokens, profile) {
|
|
|
423
454
|
}
|
|
424
455
|
const accountId = rawSubject ? normalizeProviderSubject(rawSubject) : void 0;
|
|
425
456
|
if (!accountId) return void 0;
|
|
426
|
-
const issuer =
|
|
457
|
+
const issuer = await resolveOAuthAccountIssuer({
|
|
458
|
+
providerId: provider.id,
|
|
459
|
+
identityStrategy,
|
|
460
|
+
accountIssuer: keyedProvider.accountIssuer,
|
|
461
|
+
context
|
|
462
|
+
});
|
|
427
463
|
if (!issuer) return void 0;
|
|
428
464
|
return {
|
|
429
465
|
issuer,
|
|
@@ -446,7 +482,8 @@ function instrumentSocialProviders(providers) {
|
|
|
446
482
|
if (user) try {
|
|
447
483
|
const endpointCtx = await getCurrentAuthContext();
|
|
448
484
|
const adapter = endpointCtx.context.internalAdapter;
|
|
449
|
-
const
|
|
485
|
+
const identityStrategy = resolveAccountIdentityStrategy(endpointCtx.context.options.account);
|
|
486
|
+
const accountKey = result.data && typeof adapter.findAccountOwnerByKey === "function" ? await resolveAccountKey(provider, token, result.data, identityStrategy) : void 0;
|
|
450
487
|
endpointCtx.context[OAUTH_CALLBACK_USER] = {
|
|
451
488
|
user,
|
|
452
489
|
accountKey
|
|
@@ -1052,6 +1089,49 @@ function resolveClientIpFromHeaders(headers, ipAddressHeaders) {
|
|
|
1052
1089
|
* when a request includes an X-Request-Id header.
|
|
1053
1090
|
*/
|
|
1054
1091
|
const IDENTIFICATION_COOKIE_NAME = "__infra-rid";
|
|
1092
|
+
/** GET routes that need identification (OAuth callbacks, verify links, etc.). */
|
|
1093
|
+
const IDENTIFICATION_GET_ROUTES = [
|
|
1094
|
+
routes.SIGN_IN_SOCIAL_CALLBACK,
|
|
1095
|
+
routes.SIGN_IN_OAUTH_CALLBACK,
|
|
1096
|
+
routes.DASH_IMPERSONATE_USER,
|
|
1097
|
+
routes.VERIFY_EMAIL,
|
|
1098
|
+
routes.MAGIC_LINK_VERIFY,
|
|
1099
|
+
routes.DASH_ACCEPT_INVITATION,
|
|
1100
|
+
routes.DASH_COMPLETE_INVITATION_SOCIAL
|
|
1101
|
+
];
|
|
1102
|
+
/** GET OAuth callbacks where user creation can run without identify headers. */
|
|
1103
|
+
const IDENTIFICATION_OAUTH_CALLBACK_GET_ROUTES = [routes.SIGN_IN_SOCIAL_CALLBACK, routes.SIGN_IN_OAUTH_CALLBACK];
|
|
1104
|
+
/**
|
|
1105
|
+
* Whether identification middleware should run for this request.
|
|
1106
|
+
* Non-GET always runs; GET only for routes that need cookie/KV resolution.
|
|
1107
|
+
*/
|
|
1108
|
+
function shouldRunIdentification(ctx, getRoutes = IDENTIFICATION_GET_ROUTES) {
|
|
1109
|
+
if (ctx.request?.method !== "GET") return true;
|
|
1110
|
+
return matchesAnyRoute(ctx.path, [...getRoutes]);
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Persist `X-Request-Id` as `__infra-rid` so redirect-flow OAuth callbacks
|
|
1114
|
+
* (GET, no identify headers) can resolve visitor identification from KV.
|
|
1115
|
+
* Must run in an after hook so Set-Cookie reaches the response.
|
|
1116
|
+
*/
|
|
1117
|
+
function createIdentificationCookieAfterMiddleware() {
|
|
1118
|
+
return createAuthMiddleware(async (ctx) => {
|
|
1119
|
+
const headerRequestId = ctx.request?.headers.get("X-Request-Id");
|
|
1120
|
+
if (headerRequestId) {
|
|
1121
|
+
ctx.setCookie(IDENTIFICATION_COOKIE_NAME, headerRequestId, {
|
|
1122
|
+
maxAge: 600,
|
|
1123
|
+
sameSite: "lax",
|
|
1124
|
+
httpOnly: true,
|
|
1125
|
+
path: "/"
|
|
1126
|
+
});
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (ctx.context.requestId) ctx.setCookie(IDENTIFICATION_COOKIE_NAME, "", {
|
|
1130
|
+
maxAge: 0,
|
|
1131
|
+
path: "/"
|
|
1132
|
+
});
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1055
1135
|
const identificationCache = /* @__PURE__ */ new Map();
|
|
1056
1136
|
const CACHE_TTL_MS = 6e4;
|
|
1057
1137
|
const CACHE_MAX_SIZE = 1e3;
|
|
@@ -1296,8 +1376,9 @@ function isEmailNormalizationEnabled(security) {
|
|
|
1296
1376
|
* @param $api — Dash client from `createAPI(opts, { throw: true })`.
|
|
1297
1377
|
*/
|
|
1298
1378
|
function createSecurityClient(conn, $api, options, onSecurityEvent) {
|
|
1379
|
+
const resolvedApiUrl = conn.apiUrl || INFRA_API_URL;
|
|
1299
1380
|
const emailSender = createEmailSender({
|
|
1300
|
-
apiUrl:
|
|
1381
|
+
apiUrl: resolvedApiUrl,
|
|
1301
1382
|
apiKey: conn.apiKey
|
|
1302
1383
|
});
|
|
1303
1384
|
function logEvent(event) {
|
|
@@ -1741,7 +1822,7 @@ const authPaths = [
|
|
|
1741
1822
|
"/email-otp/send-verification-otp"
|
|
1742
1823
|
];
|
|
1743
1824
|
const registration = new Set(registrationPaths);
|
|
1744
|
-
const all = new Set([...registrationPaths, ...authPaths]);
|
|
1825
|
+
const all = /* @__PURE__ */ new Set([...registrationPaths, ...authPaths]);
|
|
1745
1826
|
/** Path carries an email we hook for normalization + syntax validation. */
|
|
1746
1827
|
const allEmail = ({ path }) => !!path && all.has(path);
|
|
1747
1828
|
/**
|
|
@@ -1754,11 +1835,11 @@ const registrationEmail = ({ path }) => !!path && registration.has(path);
|
|
|
1754
1835
|
/**
|
|
1755
1836
|
* Gmail-like providers that ignore dots in the local part
|
|
1756
1837
|
*/
|
|
1757
|
-
const GMAIL_LIKE_DOMAINS = new Set(["gmail.com", "googlemail.com"]);
|
|
1838
|
+
const GMAIL_LIKE_DOMAINS = /* @__PURE__ */ new Set(["gmail.com", "googlemail.com"]);
|
|
1758
1839
|
/**
|
|
1759
1840
|
* Providers known to support plus addressing
|
|
1760
1841
|
*/
|
|
1761
|
-
const PLUS_ADDRESSING_DOMAINS = new Set([
|
|
1842
|
+
const PLUS_ADDRESSING_DOMAINS = /* @__PURE__ */ new Set([
|
|
1762
1843
|
"gmail.com",
|
|
1763
1844
|
"googlemail.com",
|
|
1764
1845
|
"outlook.com",
|
|
@@ -2028,7 +2109,7 @@ function createEmailHooks(options = {}) {
|
|
|
2028
2109
|
* Common fake/test phone numbers that should be blocked
|
|
2029
2110
|
* These are numbers commonly used in testing, movies, documentation, etc.
|
|
2030
2111
|
*/
|
|
2031
|
-
const INVALID_PHONE_NUMBERS = new Set([
|
|
2112
|
+
const INVALID_PHONE_NUMBERS = /* @__PURE__ */ new Set([
|
|
2032
2113
|
"+15550000000",
|
|
2033
2114
|
"+15550001111",
|
|
2034
2115
|
"+15550001234",
|
|
@@ -2117,7 +2198,7 @@ const INVALID_PHONE_PATTERNS = [
|
|
|
2117
2198
|
* Key: country code, Value: set of invalid prefixes
|
|
2118
2199
|
*/
|
|
2119
2200
|
const INVALID_PREFIXES_BY_COUNTRY = {
|
|
2120
|
-
US: new Set([
|
|
2201
|
+
US: /* @__PURE__ */ new Set([
|
|
2121
2202
|
"555",
|
|
2122
2203
|
"000",
|
|
2123
2204
|
"111",
|
|
@@ -2125,17 +2206,17 @@ const INVALID_PREFIXES_BY_COUNTRY = {
|
|
|
2125
2206
|
"411",
|
|
2126
2207
|
"611"
|
|
2127
2208
|
]),
|
|
2128
|
-
CA: new Set([
|
|
2209
|
+
CA: /* @__PURE__ */ new Set([
|
|
2129
2210
|
"555",
|
|
2130
2211
|
"000",
|
|
2131
2212
|
"911"
|
|
2132
2213
|
]),
|
|
2133
|
-
GB: new Set([
|
|
2214
|
+
GB: /* @__PURE__ */ new Set([
|
|
2134
2215
|
"7700900",
|
|
2135
2216
|
"1632960",
|
|
2136
2217
|
"1134960"
|
|
2137
2218
|
]),
|
|
2138
|
-
AU: new Set([
|
|
2219
|
+
AU: /* @__PURE__ */ new Set([
|
|
2139
2220
|
"0491570",
|
|
2140
2221
|
"0491571",
|
|
2141
2222
|
"0491572"
|
|
@@ -2205,7 +2286,7 @@ const validatePhone = (phone, options = {}) => {
|
|
|
2205
2286
|
if (blockVoip && phoneType === "VOIP") return false;
|
|
2206
2287
|
return true;
|
|
2207
2288
|
};
|
|
2208
|
-
const allPhonePaths = new Set([
|
|
2289
|
+
const allPhonePaths = /* @__PURE__ */ new Set([
|
|
2209
2290
|
"/phone-number/send-otp",
|
|
2210
2291
|
"/phone-number/verify",
|
|
2211
2292
|
"/sign-in/phone-number",
|
|
@@ -2463,7 +2544,7 @@ const sentinel = (options) => {
|
|
|
2463
2544
|
hooks: {
|
|
2464
2545
|
before: [
|
|
2465
2546
|
{
|
|
2466
|
-
matcher: (ctx) => ctx
|
|
2547
|
+
matcher: (ctx) => shouldRunIdentification(ctx, IDENTIFICATION_OAUTH_CALLBACK_GET_ROUTES),
|
|
2467
2548
|
handler: createIdentificationMiddleware($kv, {
|
|
2468
2549
|
skipIdentification: (ctx) => isDashRoute(ctx.path),
|
|
2469
2550
|
retry: opts.kvOptions.retry
|
|
@@ -2565,74 +2646,81 @@ const sentinel = (options) => {
|
|
|
2565
2646
|
})
|
|
2566
2647
|
}
|
|
2567
2648
|
],
|
|
2568
|
-
after: [
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2649
|
+
after: [
|
|
2650
|
+
{
|
|
2651
|
+
matcher: (ctx) => shouldRunIdentification(ctx, IDENTIFICATION_OAUTH_CALLBACK_GET_ROUTES),
|
|
2652
|
+
handler: createIdentificationCookieAfterMiddleware()
|
|
2653
|
+
},
|
|
2654
|
+
{
|
|
2655
|
+
matcher: (ctx) => !!opts.security?.staleUsers?.enabled && !isDashRoute(ctx.path),
|
|
2656
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
2657
|
+
if (ctx.context.returned instanceof Error) return;
|
|
2658
|
+
const created = ctx.context.newSession;
|
|
2659
|
+
const userId = created?.user?.id ?? created?.session?.userId;
|
|
2660
|
+
const sessionToken = created?.session?.token;
|
|
2661
|
+
if (!userId || !sessionToken) return;
|
|
2662
|
+
let user = created?.user ?? null;
|
|
2663
|
+
try {
|
|
2664
|
+
user = await getUserById(userId, ctx, { includeLastActiveAt: activityTrackingEnabled }) ?? user;
|
|
2665
|
+
} catch (error) {
|
|
2666
|
+
logger.warn("[Sentinel] Failed to fetch user for stale-account check:", error);
|
|
2667
|
+
if (!user) return;
|
|
2668
|
+
}
|
|
2581
2669
|
if (!user) return;
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
}
|
|
2635
|
-
|
|
2670
|
+
recordCheck(ctx, "stale_users");
|
|
2671
|
+
const staleCheck = await securityService.checkStaleUser(userId, activityTrackingEnabled ? user.lastActiveAt ?? null : null);
|
|
2672
|
+
if (!staleCheck.isStale) return;
|
|
2673
|
+
const identification = ctx.context.identification;
|
|
2674
|
+
const staleOpts = opts.security?.staleUsers;
|
|
2675
|
+
const notificationPromises = [];
|
|
2676
|
+
if (staleCheck.notifyUser && user.email) notificationPromises.push(securityService.notifyStaleAccountUser(user.email, user.name || null, staleCheck.daysSinceLastActive || 0, identification));
|
|
2677
|
+
if (staleCheck.notifyAdmin && staleOpts?.adminEmail) notificationPromises.push(securityService.notifyStaleAccountAdmin(staleOpts.adminEmail, userId, user.email || "unknown", user.name || null, staleCheck.daysSinceLastActive || 0, identification));
|
|
2678
|
+
if (notificationPromises.length > 0) Promise.all(notificationPromises).catch((error) => {
|
|
2679
|
+
logger.error("[Sentinel] Failed to send stale account notifications:", error);
|
|
2680
|
+
});
|
|
2681
|
+
if (staleCheck.action !== "block") return;
|
|
2682
|
+
setOutcome(ctx, "blocked", "stale_users", {
|
|
2683
|
+
userId,
|
|
2684
|
+
daysSinceLastActive: staleCheck.daysSinceLastActive,
|
|
2685
|
+
staleDays: staleCheck.staleDays,
|
|
2686
|
+
lastActiveAt: staleCheck.lastActiveAt,
|
|
2687
|
+
notifyUser: staleCheck.notifyUser,
|
|
2688
|
+
notifyAdmin: staleCheck.notifyAdmin
|
|
2689
|
+
});
|
|
2690
|
+
emitEvaluation(ctx, trackEvent);
|
|
2691
|
+
try {
|
|
2692
|
+
await ctx.context.internalAdapter.deleteSession(sessionToken);
|
|
2693
|
+
} catch (error) {
|
|
2694
|
+
logger.warn("[Sentinel] Failed to delete stale-blocked session:", error);
|
|
2695
|
+
}
|
|
2696
|
+
deleteSessionCookie(ctx);
|
|
2697
|
+
ctx.context.setNewSession(null);
|
|
2698
|
+
throw new APIError("FORBIDDEN", STALE_ACCOUNT_BLOCK_ERROR);
|
|
2699
|
+
})
|
|
2700
|
+
},
|
|
2701
|
+
{
|
|
2702
|
+
matcher: (ctx) => ctx.request?.method !== "GET" && !isDashRoute(ctx.path),
|
|
2703
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
2704
|
+
const untrustedVisitorId = ctx.context.untrustedVisitorId;
|
|
2705
|
+
const ip = ctx.context.ip;
|
|
2706
|
+
const body = ctx.body;
|
|
2707
|
+
const loginId = matchesAnyRoute(ctx.path, [routes.SIGN_IN_USERNAME]) ? body?.username : body?.email;
|
|
2708
|
+
const isPasswordSignInRoute = matchesAnyRoute(ctx.path, [
|
|
2709
|
+
routes.SIGN_IN_EMAIL,
|
|
2710
|
+
routes.SIGN_IN_USERNAME,
|
|
2711
|
+
routes.SIGN_IN_EMAIL_OTP
|
|
2712
|
+
]);
|
|
2713
|
+
emitEvaluation(ctx, trackEvent, {
|
|
2714
|
+
identifier: loginId,
|
|
2715
|
+
userAgent: ctx.headers?.get?.("user-agent") || ""
|
|
2716
|
+
});
|
|
2717
|
+
const returned = ctx.context.returned;
|
|
2718
|
+
const staleBlocked = isStaleAccountError(returned);
|
|
2719
|
+
if (isPasswordSignInRoute && returned instanceof Error && !staleBlocked && loginId && body?.password && untrustedVisitorId) await ctx.context.runInBackgroundOrAwait(securityService.trackFailedAttempt(loginId, untrustedVisitorId, body.password, ip, ctx.context.requestId ?? null));
|
|
2720
|
+
if (isPasswordSignInRoute && (!(returned instanceof Error) || staleBlocked) && loginId) await ctx.context.runInBackgroundOrAwait(securityService.clearFailedAttempts(loginId));
|
|
2721
|
+
})
|
|
2722
|
+
}
|
|
2723
|
+
]
|
|
2636
2724
|
}
|
|
2637
2725
|
};
|
|
2638
2726
|
};
|
|
@@ -2786,45 +2874,131 @@ const ALL_SCIM_SCOPES = [
|
|
|
2786
2874
|
"scim.groups.read",
|
|
2787
2875
|
"scim.groups.write"
|
|
2788
2876
|
];
|
|
2789
|
-
const scimScopeSchema = z
|
|
2877
|
+
const scimScopeSchema = z.enum(ALL_SCIM_SCOPES);
|
|
2790
2878
|
const credentialPolicySchema = {
|
|
2791
|
-
scopes: z
|
|
2792
|
-
expiresAt: z
|
|
2879
|
+
scopes: z.array(scimScopeSchema).min(1).optional(),
|
|
2880
|
+
expiresAt: z.coerce.date().optional()
|
|
2793
2881
|
};
|
|
2794
|
-
const directorySyncSSOPairingSchema = z
|
|
2795
|
-
ssoProviderId: z
|
|
2796
|
-
protocol: z
|
|
2797
|
-
externalIdSource: z
|
|
2798
|
-
kind: z
|
|
2799
|
-
name: z
|
|
2882
|
+
const directorySyncSSOPairingSchema = z.discriminatedUnion("protocol", [z.object({
|
|
2883
|
+
ssoProviderId: z.string().trim().min(1).max(255),
|
|
2884
|
+
protocol: z.literal("oidc"),
|
|
2885
|
+
externalIdSource: z.discriminatedUnion("kind", [z.object({ kind: z.literal("subject") }), z.object({
|
|
2886
|
+
kind: z.literal("verifiedIdTokenClaim"),
|
|
2887
|
+
name: z.string().trim().min(1).max(255)
|
|
2800
2888
|
})])
|
|
2801
|
-
}), z
|
|
2802
|
-
ssoProviderId: z
|
|
2803
|
-
protocol: z
|
|
2804
|
-
externalIdSource: z
|
|
2805
|
-
kind: z
|
|
2806
|
-
name: z
|
|
2889
|
+
}), z.object({
|
|
2890
|
+
ssoProviderId: z.string().trim().min(1).max(255),
|
|
2891
|
+
protocol: z.literal("saml"),
|
|
2892
|
+
externalIdSource: z.discriminatedUnion("kind", [z.object({ kind: z.literal("nameId") }), z.object({
|
|
2893
|
+
kind: z.literal("attribute"),
|
|
2894
|
+
name: z.string().trim().min(1).max(255)
|
|
2807
2895
|
})])
|
|
2808
2896
|
})]);
|
|
2809
|
-
const createDirectoryBodySchema = z
|
|
2810
|
-
providerId: z
|
|
2897
|
+
const createDirectoryBodySchema = z.object({
|
|
2898
|
+
providerId: z.string().trim().min(1).max(255),
|
|
2811
2899
|
pairing: directorySyncSSOPairingSchema.optional(),
|
|
2812
2900
|
...credentialPolicySchema
|
|
2813
2901
|
});
|
|
2814
|
-
const rotateCredentialBodySchema = z
|
|
2815
|
-
const emptyBodySchema = z
|
|
2902
|
+
const rotateCredentialBodySchema = z.object(credentialPolicySchema);
|
|
2903
|
+
const emptyBodySchema = z.object({});
|
|
2816
2904
|
function setCredentialResponseSecurityHeaders(ctx) {
|
|
2817
2905
|
ctx.setHeader("Cache-Control", "no-store, max-age=0");
|
|
2818
2906
|
ctx.setHeader("Pragma", "no-cache");
|
|
2819
2907
|
ctx.setHeader("Referrer-Policy", "no-referrer");
|
|
2820
2908
|
}
|
|
2821
2909
|
//#endregion
|
|
2822
|
-
//#region src/directory-sync/
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2910
|
+
//#region src/directory-sync/saml-policy.ts
|
|
2911
|
+
/**
|
|
2912
|
+
* Aligned with `@better-auth/sso` SAML metadata constants / helpers.
|
|
2913
|
+
* @see better-auth packages/sso parseSAMLServiceProviderMetadata
|
|
2914
|
+
*/
|
|
2915
|
+
const SAML_METADATA_NAMESPACE = "urn:oasis:names:tc:SAML:2.0:metadata";
|
|
2916
|
+
const HTTP_POST_BINDING = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
|
|
2917
|
+
/**
|
|
2918
|
+
* Whether a SAML provider config requires signed assertions.
|
|
2919
|
+
*
|
|
2920
|
+
* Matches `@better-auth/sso` `deriveSAMLServiceProviderPolicy(...).wantAssertionsSigned`.
|
|
2921
|
+
* Invalid or unusable custom SP metadata throws (pairing maps this to BAD_REQUEST).
|
|
2922
|
+
*/
|
|
2923
|
+
function requiresSignedSAMLAssertions(config) {
|
|
2924
|
+
const metadata = config.spMetadata?.metadata;
|
|
2925
|
+
if (!metadata) return config.wantAssertionsSigned === true;
|
|
2926
|
+
const parsedMetadata = parseSAMLServiceProviderMetadata(metadata);
|
|
2927
|
+
if (!parsedMetadata.postAssertionConsumerServiceUrls.length) throw new Error("Unusable SAML service provider metadata");
|
|
2928
|
+
return parsedMetadata.wantAssertionsSigned;
|
|
2929
|
+
}
|
|
2930
|
+
/**
|
|
2931
|
+
* Parses the security- and identity-relevant parts of SAML service-provider
|
|
2932
|
+
* metadata without relying on local-name-only XML matching.
|
|
2933
|
+
*
|
|
2934
|
+
* Port of `@better-auth/sso` `parseSAMLServiceProviderMetadata`.
|
|
2935
|
+
*/
|
|
2936
|
+
function parseSAMLServiceProviderMetadata(metadata) {
|
|
2937
|
+
const document = new DOMParser({ onError: (_level, message) => {
|
|
2938
|
+
throw new Error(message);
|
|
2939
|
+
} }).parseFromString(metadata, "text/xml");
|
|
2940
|
+
const entityDescriptor = document.documentElement;
|
|
2941
|
+
if (!entityDescriptor || entityDescriptor.localName !== "EntityDescriptor" || entityDescriptor.namespaceURI !== SAML_METADATA_NAMESPACE) throw new Error("Invalid SAML EntityDescriptor");
|
|
2942
|
+
const entityID = entityDescriptor.getAttribute("entityID")?.trim();
|
|
2943
|
+
if (!entityID) throw new Error("Missing SAML entityID");
|
|
2944
|
+
const serviceProviderDescriptors = directMetadataChildren(entityDescriptor, "SPSSODescriptor");
|
|
2945
|
+
if (serviceProviderDescriptors.length === 0) throw new Error("SAML metadata must contain an SPSSODescriptor");
|
|
2946
|
+
const acceptedServiceProviderDescriptors = new Set(serviceProviderDescriptors);
|
|
2947
|
+
for (const element of Array.from(document.getElementsByTagName("*"))) {
|
|
2948
|
+
if (element.localName === "EntityDescriptor" && element !== entityDescriptor) throw new Error("Invalid nested SAML EntityDescriptor");
|
|
2949
|
+
if (element.localName === "SPSSODescriptor" && !acceptedServiceProviderDescriptors.has(element)) throw new Error("Invalid SAML SPSSODescriptor namespace or position");
|
|
2950
|
+
if ((element.localName === "AssertionConsumerService" || element.localName === "NameIDFormat") && (element.namespaceURI !== SAML_METADATA_NAMESPACE || !element.parentNode || !isElement(element.parentNode) || !acceptedServiceProviderDescriptors.has(element.parentNode))) throw new Error(`Invalid SAML ${element.localName} namespace or position`);
|
|
2951
|
+
}
|
|
2952
|
+
const postAssertionConsumerServiceUrls = [];
|
|
2953
|
+
const nameIDFormats = [];
|
|
2954
|
+
let wantAssertionsSigned = false;
|
|
2955
|
+
for (const descriptor of serviceProviderDescriptors) {
|
|
2956
|
+
wantAssertionsSigned = parseXMLSchemaBoolean(descriptor.getAttribute("WantAssertionsSigned")) || wantAssertionsSigned;
|
|
2957
|
+
for (const nameIDFormat of directMetadataChildren(descriptor, "NameIDFormat")) {
|
|
2958
|
+
const value = nameIDFormat.textContent?.trim();
|
|
2959
|
+
if (value) nameIDFormats.push(value);
|
|
2960
|
+
}
|
|
2961
|
+
for (const service of directMetadataChildren(descriptor, "AssertionConsumerService")) {
|
|
2962
|
+
if (service.getAttribute("Binding") !== HTTP_POST_BINDING) continue;
|
|
2963
|
+
const location = service.getAttribute("Location")?.trim();
|
|
2964
|
+
if (!location || !isAbsoluteHttpUrl(location)) throw new Error("Invalid SAML POST AssertionConsumerService");
|
|
2965
|
+
postAssertionConsumerServiceUrls.push(location);
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
return {
|
|
2969
|
+
entityID,
|
|
2970
|
+
nameIDFormats: [...new Set(nameIDFormats)],
|
|
2971
|
+
postAssertionConsumerServiceUrls: [...new Set(postAssertionConsumerServiceUrls)],
|
|
2972
|
+
wantAssertionsSigned
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
function isElement(node) {
|
|
2976
|
+
return node.nodeType === 1;
|
|
2977
|
+
}
|
|
2978
|
+
function directMetadataChildren(element, localName) {
|
|
2979
|
+
return Array.from(element.childNodes).filter((node) => isElement(node) && node.localName === localName && node.namespaceURI === SAML_METADATA_NAMESPACE);
|
|
2827
2980
|
}
|
|
2981
|
+
function parseXMLSchemaBoolean(value) {
|
|
2982
|
+
if (value === null) return false;
|
|
2983
|
+
switch (value.trim()) {
|
|
2984
|
+
case "true":
|
|
2985
|
+
case "1": return true;
|
|
2986
|
+
case "false":
|
|
2987
|
+
case "0": return false;
|
|
2988
|
+
default: throw new Error("Invalid XML Schema boolean");
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
function isAbsoluteHttpUrl(value) {
|
|
2992
|
+
if (value.includes("#")) return false;
|
|
2993
|
+
try {
|
|
2994
|
+
const url = new URL(value);
|
|
2995
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
2996
|
+
} catch {
|
|
2997
|
+
return false;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
//#endregion
|
|
3001
|
+
//#region src/directory-sync/pairing.ts
|
|
2828
3002
|
function isRecord$1(value) {
|
|
2829
3003
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2830
3004
|
}
|
|
@@ -2877,10 +3051,9 @@ async function resolveDirectorySyncSSOPairing(ctx, organizationId, pairing) {
|
|
|
2877
3051
|
if (!oidcConfiguration || samlConfiguration) throw ctx.error("BAD_REQUEST", { message: "The selected SSO provider is not an OIDC provider" });
|
|
2878
3052
|
} else {
|
|
2879
3053
|
if (!samlConfiguration || oidcConfiguration) throw ctx.error("BAD_REQUEST", { message: "The selected SSO provider is not a SAML provider" });
|
|
2880
|
-
const samlPolicy = await loadSAMLPolicy();
|
|
2881
3054
|
let wantAssertionsSigned = false;
|
|
2882
3055
|
try {
|
|
2883
|
-
wantAssertionsSigned =
|
|
3056
|
+
wantAssertionsSigned = requiresSignedSAMLAssertions({
|
|
2884
3057
|
spMetadata: isRecord$1(samlConfiguration.spMetadata) ? { metadata: typeof samlConfiguration.spMetadata.metadata === "string" ? samlConfiguration.spMetadata.metadata : void 0 } : void 0,
|
|
2885
3058
|
wantAssertionsSigned: samlConfiguration.wantAssertionsSigned === true
|
|
2886
3059
|
});
|
|
@@ -2927,17 +3100,228 @@ async function guardDirectorySyncSSOProviderMutation(input, context) {
|
|
|
2927
3100
|
}
|
|
2928
3101
|
}
|
|
2929
3102
|
//#endregion
|
|
3103
|
+
//#region src/directory-sync/scim-user-link.ts
|
|
3104
|
+
function createScopedKey(parts) {
|
|
3105
|
+
return base64Url.encode(sha256(new TextEncoder().encode(JSON.stringify(parts))), { padding: false });
|
|
3106
|
+
}
|
|
3107
|
+
function createSCIMUserExternalIdKey(connectionId, externalId) {
|
|
3108
|
+
return createScopedKey([
|
|
3109
|
+
"scim-user-external-id",
|
|
3110
|
+
connectionId,
|
|
3111
|
+
externalId
|
|
3112
|
+
]);
|
|
3113
|
+
}
|
|
3114
|
+
function createSCIMConnectionKey(connectionId) {
|
|
3115
|
+
return createScopedKey(["scim-connection", connectionId]);
|
|
3116
|
+
}
|
|
3117
|
+
var SCIMIdentityMutationConflict = class extends Error {
|
|
3118
|
+
constructor() {
|
|
3119
|
+
super("The SCIM identity changed concurrently; retry the request");
|
|
3120
|
+
this.name = "SCIMIdentityMutationConflict";
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
3123
|
+
function concurrentIdentityMutation() {
|
|
3124
|
+
throw new SCIMIdentityMutationConflict();
|
|
3125
|
+
}
|
|
3126
|
+
function hasIncrementOne(database) {
|
|
3127
|
+
return typeof database.incrementOne === "function";
|
|
3128
|
+
}
|
|
3129
|
+
async function tryFenceActiveSCIMConnection(database, connectionId) {
|
|
3130
|
+
return database.incrementOne({
|
|
3131
|
+
model: "scimConnectionBinding",
|
|
3132
|
+
where: [
|
|
3133
|
+
{
|
|
3134
|
+
field: "connectionKey",
|
|
3135
|
+
value: createSCIMConnectionKey(connectionId)
|
|
3136
|
+
},
|
|
3137
|
+
{
|
|
3138
|
+
field: "connectionId",
|
|
3139
|
+
value: connectionId
|
|
3140
|
+
},
|
|
3141
|
+
{
|
|
3142
|
+
field: "decommissionStatus",
|
|
3143
|
+
value: "active"
|
|
3144
|
+
}
|
|
3145
|
+
],
|
|
3146
|
+
increment: { decommissionRevision: 1 }
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
/**
|
|
3150
|
+
* Acquires an active provisioned User link inside the caller's transaction.
|
|
3151
|
+
* Returns null when no active link exists. Throws on concurrent identity mutation.
|
|
3152
|
+
*/
|
|
3153
|
+
async function acquireActiveSCIMUserLink(reference, context) {
|
|
3154
|
+
if (!hasIncrementOne(context.database)) return null;
|
|
3155
|
+
const database = context.database;
|
|
3156
|
+
const externalIdKey = createSCIMUserExternalIdKey(reference.connectionId, reference.externalId);
|
|
3157
|
+
const source = await database.findOne({
|
|
3158
|
+
model: "scimUser",
|
|
3159
|
+
where: [
|
|
3160
|
+
{
|
|
3161
|
+
field: "connectionId",
|
|
3162
|
+
value: reference.connectionId
|
|
3163
|
+
},
|
|
3164
|
+
{
|
|
3165
|
+
field: "externalIdKey",
|
|
3166
|
+
value: externalIdKey
|
|
3167
|
+
},
|
|
3168
|
+
{
|
|
3169
|
+
field: "externalId",
|
|
3170
|
+
value: reference.externalId
|
|
3171
|
+
},
|
|
3172
|
+
{
|
|
3173
|
+
field: "active",
|
|
3174
|
+
value: true
|
|
3175
|
+
}
|
|
3176
|
+
]
|
|
3177
|
+
});
|
|
3178
|
+
if (!source) return null;
|
|
3179
|
+
const binding = await database.findOne({
|
|
3180
|
+
model: "scimConnectionBinding",
|
|
3181
|
+
where: [
|
|
3182
|
+
{
|
|
3183
|
+
field: "connectionKey",
|
|
3184
|
+
value: createSCIMConnectionKey(reference.connectionId)
|
|
3185
|
+
},
|
|
3186
|
+
{
|
|
3187
|
+
field: "connectionId",
|
|
3188
|
+
value: reference.connectionId
|
|
3189
|
+
},
|
|
3190
|
+
{
|
|
3191
|
+
field: "decommissionStatus",
|
|
3192
|
+
value: "active"
|
|
3193
|
+
}
|
|
3194
|
+
]
|
|
3195
|
+
});
|
|
3196
|
+
if (!binding || binding.provisioningDomainId !== source.provisioningDomainId) return null;
|
|
3197
|
+
if (await database.findOne({
|
|
3198
|
+
model: "scimIdentityTombstone",
|
|
3199
|
+
where: [
|
|
3200
|
+
{
|
|
3201
|
+
field: "connectionId",
|
|
3202
|
+
value: reference.connectionId
|
|
3203
|
+
},
|
|
3204
|
+
{
|
|
3205
|
+
field: "externalIdKey",
|
|
3206
|
+
value: externalIdKey
|
|
3207
|
+
},
|
|
3208
|
+
{
|
|
3209
|
+
field: "externalId",
|
|
3210
|
+
value: reference.externalId
|
|
3211
|
+
}
|
|
3212
|
+
]
|
|
3213
|
+
})) return null;
|
|
3214
|
+
const subject = await database.findOne({
|
|
3215
|
+
model: "scimSubject",
|
|
3216
|
+
where: [{
|
|
3217
|
+
field: "userId",
|
|
3218
|
+
value: source.userId
|
|
3219
|
+
}]
|
|
3220
|
+
});
|
|
3221
|
+
if (!subject) return null;
|
|
3222
|
+
if (!await database.findOne({
|
|
3223
|
+
model: "user",
|
|
3224
|
+
where: [{
|
|
3225
|
+
field: "id",
|
|
3226
|
+
value: source.userId
|
|
3227
|
+
}]
|
|
3228
|
+
})) return null;
|
|
3229
|
+
const acquiredSubject = await database.incrementOne({
|
|
3230
|
+
model: "scimSubject",
|
|
3231
|
+
where: [
|
|
3232
|
+
{
|
|
3233
|
+
field: "id",
|
|
3234
|
+
value: subject.id
|
|
3235
|
+
},
|
|
3236
|
+
{
|
|
3237
|
+
field: "userId",
|
|
3238
|
+
value: source.userId
|
|
3239
|
+
},
|
|
3240
|
+
{
|
|
3241
|
+
field: "revision",
|
|
3242
|
+
value: subject.revision
|
|
3243
|
+
}
|
|
3244
|
+
],
|
|
3245
|
+
increment: { revision: 1 },
|
|
3246
|
+
set: { updatedAt: /* @__PURE__ */ new Date() }
|
|
3247
|
+
});
|
|
3248
|
+
if (!acquiredSubject) concurrentIdentityMutation();
|
|
3249
|
+
const acquiredSource = await database.findOne({
|
|
3250
|
+
model: "scimUser",
|
|
3251
|
+
where: [
|
|
3252
|
+
{
|
|
3253
|
+
field: "id",
|
|
3254
|
+
value: source.id
|
|
3255
|
+
},
|
|
3256
|
+
{
|
|
3257
|
+
field: "connectionId",
|
|
3258
|
+
value: reference.connectionId
|
|
3259
|
+
},
|
|
3260
|
+
{
|
|
3261
|
+
field: "provisioningDomainId",
|
|
3262
|
+
value: binding.provisioningDomainId
|
|
3263
|
+
},
|
|
3264
|
+
{
|
|
3265
|
+
field: "userId",
|
|
3266
|
+
value: source.userId
|
|
3267
|
+
},
|
|
3268
|
+
{
|
|
3269
|
+
field: "connectionUserKey",
|
|
3270
|
+
value: source.connectionUserKey
|
|
3271
|
+
},
|
|
3272
|
+
{
|
|
3273
|
+
field: "externalIdKey",
|
|
3274
|
+
value: externalIdKey
|
|
3275
|
+
},
|
|
3276
|
+
{
|
|
3277
|
+
field: "externalId",
|
|
3278
|
+
value: reference.externalId
|
|
3279
|
+
},
|
|
3280
|
+
{
|
|
3281
|
+
field: "active",
|
|
3282
|
+
value: true
|
|
3283
|
+
}
|
|
3284
|
+
]
|
|
3285
|
+
});
|
|
3286
|
+
if (!acquiredSource || !acquiredSource.active || acquiredSource.userId !== acquiredSubject.userId) concurrentIdentityMutation();
|
|
3287
|
+
if (!await database.findOne({
|
|
3288
|
+
model: "user",
|
|
3289
|
+
where: [{
|
|
3290
|
+
field: "id",
|
|
3291
|
+
value: acquiredSource.userId
|
|
3292
|
+
}]
|
|
3293
|
+
})) concurrentIdentityMutation();
|
|
3294
|
+
if (await database.findOne({
|
|
3295
|
+
model: "scimIdentityTombstone",
|
|
3296
|
+
where: [
|
|
3297
|
+
{
|
|
3298
|
+
field: "connectionId",
|
|
3299
|
+
value: reference.connectionId
|
|
3300
|
+
},
|
|
3301
|
+
{
|
|
3302
|
+
field: "externalIdKey",
|
|
3303
|
+
value: externalIdKey
|
|
3304
|
+
},
|
|
3305
|
+
{
|
|
3306
|
+
field: "externalId",
|
|
3307
|
+
value: reference.externalId
|
|
3308
|
+
}
|
|
3309
|
+
]
|
|
3310
|
+
})) concurrentIdentityMutation();
|
|
3311
|
+
const acquiredBinding = await tryFenceActiveSCIMConnection(database, reference.connectionId);
|
|
3312
|
+
if (!acquiredBinding || acquiredBinding.id !== binding.id || acquiredBinding.provisioningDomainId !== acquiredSource.provisioningDomainId) concurrentIdentityMutation();
|
|
3313
|
+
return {
|
|
3314
|
+
scimUserId: source.id,
|
|
3315
|
+
userId: source.userId
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
//#endregion
|
|
2930
3319
|
//#region src/directory-sync/sso-user-resolution.ts
|
|
2931
3320
|
const GENERIC_REJECTION = {
|
|
2932
3321
|
action: "reject",
|
|
2933
3322
|
code: "DIRECTORY_SYNC_AUTHENTICATION_FAILED",
|
|
2934
3323
|
message: "Unable to sign in with this SSO connection"
|
|
2935
3324
|
};
|
|
2936
|
-
let scimCatalogModule;
|
|
2937
|
-
function loadSCIMCatalog() {
|
|
2938
|
-
scimCatalogModule ??= import("@better-auth/scim");
|
|
2939
|
-
return scimCatalogModule;
|
|
2940
|
-
}
|
|
2941
3325
|
function readStringExternalId(value) {
|
|
2942
3326
|
if (typeof value === "string" && value.length > 0) return value;
|
|
2943
3327
|
return null;
|
|
@@ -2983,9 +3367,7 @@ async function resolveOrganizationDirectorySyncUser(input, context) {
|
|
|
2983
3367
|
const externalId = readExternalId(input, pairing);
|
|
2984
3368
|
if (!externalId) return GENERIC_REJECTION;
|
|
2985
3369
|
try {
|
|
2986
|
-
const
|
|
2987
|
-
if (typeof catalog.acquireActiveSCIMUserLink !== "function") return GENERIC_REJECTION;
|
|
2988
|
-
const link = await catalog.acquireActiveSCIMUserLink({
|
|
3370
|
+
const link = await acquireActiveSCIMUserLink({
|
|
2989
3371
|
connectionId: directory.connectionId,
|
|
2990
3372
|
externalId
|
|
2991
3373
|
}, { database: context.database });
|
|
@@ -3017,35 +3399,41 @@ function instrumentDirectorySyncIntegration(ctx, options) {
|
|
|
3017
3399
|
logger.debug("[Dash] Managed directory sync enabled but SSO/SCIM plugins are not active. Skipping integration instrumentation");
|
|
3018
3400
|
return;
|
|
3019
3401
|
}
|
|
3020
|
-
if (options.ssoPairing)
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3402
|
+
if (options.ssoPairing) {
|
|
3403
|
+
if (ssoPlugin) {
|
|
3404
|
+
if (!ssoPlugin.options) logger.error("[Dash] Managed directory sync ssoPairing requires sso({ ... }) (at least sso({})) so pairing callbacks can be installed on the shared options object.");
|
|
3405
|
+
else {
|
|
3406
|
+
const ssoOptions = ssoPlugin.options;
|
|
3407
|
+
const previousResolveUser = ssoOptions.resolveUser;
|
|
3408
|
+
ssoOptions.resolveUser = async (input, context) => {
|
|
3409
|
+
const result = await resolveOrganizationDirectorySyncUser(input, context);
|
|
3410
|
+
if (result.action !== "continue") return result;
|
|
3411
|
+
if (previousResolveUser) return previousResolveUser(input, context);
|
|
3412
|
+
return { action: "continue" };
|
|
3413
|
+
};
|
|
3414
|
+
const previousGuard = ssoOptions.guardProviderMutation;
|
|
3415
|
+
ssoOptions.guardProviderMutation = async (input, context) => {
|
|
3416
|
+
await guardDirectorySyncSSOProviderMutation(input, context);
|
|
3417
|
+
if (previousGuard) await previousGuard(input, context);
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
} else logger.debug("[Dash] Managed directory sync ssoPairing enabled but SSO plugin is not active. Skipping SSO pairing instrumentation");
|
|
3035
3421
|
}
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3422
|
+
if (options.membershipProjection.enabled) {
|
|
3423
|
+
if (scimPlugin) {
|
|
3424
|
+
if (!scimPlugin.options) logger.error("[Dash] Managed directory sync membershipProjection requires scim({ ... }) with an options object so membership projection can be installed.");
|
|
3425
|
+
else {
|
|
3426
|
+
const scimOptions = scimPlugin.options;
|
|
3427
|
+
const projection = scimOptions.projection ??= {};
|
|
3428
|
+
const previousReconcileUser = projection.reconcileUser;
|
|
3429
|
+
const reconcileUser = createOrganizationMembershipProjection({ role: options.membershipProjection.role });
|
|
3430
|
+
projection.reconcileUser = async (input, context) => {
|
|
3431
|
+
await reconcileUser(input, context);
|
|
3432
|
+
if (previousReconcileUser) await previousReconcileUser(input, context);
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
} else logger.debug("[Dash] Managed directory sync membershipProjection enabled but SCIM plugin is not active. Skipping membership projection instrumentation");
|
|
3047
3436
|
}
|
|
3048
|
-
else logger.debug("[Dash] Managed directory sync membershipProjection enabled but SCIM plugin is not active. Skipping membership projection instrumentation");
|
|
3049
3437
|
}
|
|
3050
3438
|
//#endregion
|
|
3051
3439
|
//#region src/events/organization/events-invitation.ts
|
|
@@ -3462,7 +3850,7 @@ function snakeCaseToCamelCase(key) {
|
|
|
3462
3850
|
function keyVariantsForMatching(key) {
|
|
3463
3851
|
const trimmed = key.replace(/^[\s._-]+/, "");
|
|
3464
3852
|
if (!trimmed) return [key];
|
|
3465
|
-
const out = new Set([key, trimmed]);
|
|
3853
|
+
const out = /* @__PURE__ */ new Set([key, trimmed]);
|
|
3466
3854
|
if (trimmed.includes("_")) out.add(snakeCaseToCamelCase(trimmed));
|
|
3467
3855
|
return [...out];
|
|
3468
3856
|
}
|
|
@@ -3482,7 +3870,7 @@ function matchesNormalizedPatterns(key, patterns) {
|
|
|
3482
3870
|
if (!hasAnyPatterns(patterns)) return false;
|
|
3483
3871
|
const variants = keyVariantsForMatching(key);
|
|
3484
3872
|
const compactLower = key.replace(/[\s._-]/g, "").toLowerCase();
|
|
3485
|
-
const forms = new Set([compactLower]);
|
|
3873
|
+
const forms = /* @__PURE__ */ new Set([compactLower]);
|
|
3486
3874
|
for (const raw of variants) {
|
|
3487
3875
|
forms.add(raw);
|
|
3488
3876
|
forms.add(raw.toLowerCase());
|
|
@@ -3540,9 +3928,9 @@ function redact(value, options) {
|
|
|
3540
3928
|
const patterns = normalizePatterns(options?.patterns);
|
|
3541
3929
|
return redactInner(value, /* @__PURE__ */ new WeakSet(), options, patterns);
|
|
3542
3930
|
}
|
|
3543
|
-
const DASH_PLUGIN_OPTIONS_EXCLUDE_KEYS = { stripe: new Set(["stripeClient"]) };
|
|
3931
|
+
const DASH_PLUGIN_OPTIONS_EXCLUDE_KEYS = { stripe: /* @__PURE__ */ new Set(["stripeClient"]) };
|
|
3544
3932
|
/** Storage-strategy enums that match sensitive key suffixes but are not secrets. */
|
|
3545
|
-
const DASH_PLUGIN_OPTIONS_IGNORE_KEYS = new Set([
|
|
3933
|
+
const DASH_PLUGIN_OPTIONS_IGNORE_KEYS = /* @__PURE__ */ new Set([
|
|
3546
3934
|
"storeApiKey",
|
|
3547
3935
|
"storeClientSecret",
|
|
3548
3936
|
"storeSCIMToken",
|
|
@@ -3623,7 +4011,7 @@ function timingSafeEqualHash(a, b) {
|
|
|
3623
4011
|
* A freshly issued token is almost certainly legitimate.
|
|
3624
4012
|
*/
|
|
3625
4013
|
const JTI_CHECK_GRACE_PERIOD_SECONDS = 30;
|
|
3626
|
-
const JWKS_CACHE_TTL_MS =
|
|
4014
|
+
const JWKS_CACHE_TTL_MS = 9e5;
|
|
3627
4015
|
const jwksCache = /* @__PURE__ */ new Map();
|
|
3628
4016
|
const inflightRequests = /* @__PURE__ */ new Map();
|
|
3629
4017
|
async function fetchJWKS(ctx, cacheKey, $api) {
|
|
@@ -3672,7 +4060,8 @@ const jwtMiddleware = (options, schema, getJWT) => {
|
|
|
3672
4060
|
ctx.context.logger.warn("[Dash] JWT is missing from header");
|
|
3673
4061
|
throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
|
|
3674
4062
|
}
|
|
3675
|
-
const
|
|
4063
|
+
const remoteJWKs = await getJWKs(ctx, cacheKey, $api);
|
|
4064
|
+
const { payload } = await jwtVerify(jwsFromHeader, remoteJWKs, { maxTokenAge: "5m" }).catch((e) => {
|
|
3676
4065
|
ctx.context.logger.warn("[Dash] JWT verification failed:", e);
|
|
3677
4066
|
throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
|
|
3678
4067
|
});
|
|
@@ -3726,7 +4115,8 @@ const jwtValidateMiddleware = (options) => {
|
|
|
3726
4115
|
ctx.context.logger.warn("[Dash] JWT is missing from header");
|
|
3727
4116
|
throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
|
|
3728
4117
|
}
|
|
3729
|
-
const
|
|
4118
|
+
const remoteJWKs = await getJWKs(ctx, cacheKey, $api);
|
|
4119
|
+
const { payload } = await jwtVerify(jwsFromHeader, remoteJWKs, { maxTokenAge: "5m" }).catch((e) => {
|
|
3730
4120
|
ctx.context.logger.error("[Dash] JWT verification failed:", e);
|
|
3731
4121
|
throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
|
|
3732
4122
|
});
|
|
@@ -3901,6 +4291,7 @@ const getConfig = (options) => {
|
|
|
3901
4291
|
disableCSRFCheck: ctx.context.options.advanced?.disableCSRFCheck || false,
|
|
3902
4292
|
disableOriginCheck: ctx.context.options.advanced?.disableOriginCheck || false,
|
|
3903
4293
|
allowDifferentEmails: ctx.context.options.account?.accountLinking?.enabled && ctx.context.options.account?.accountLinking?.allowDifferentEmails || false,
|
|
4294
|
+
identityStrategy: getConfiguredAccountIdentityStrategy(ctx.context.options.account),
|
|
3904
4295
|
skipStateCookieCheck: ctx.context.options.account?.skipStateCookieCheck || false,
|
|
3905
4296
|
storeStateCookieStrategy: ctx.context.options.account?.storeStateStrategy || null,
|
|
3906
4297
|
cookieCache: {
|
|
@@ -4055,12 +4446,12 @@ function assertManagedDirectorySyncEnabled(ctx) {
|
|
|
4055
4446
|
}
|
|
4056
4447
|
//#endregion
|
|
4057
4448
|
//#region src/routes/directory-sync/route-contract.ts
|
|
4058
|
-
const DIRECTORY_SYNC_CREDENTIAL_LIFETIME_MS =
|
|
4059
|
-
const directorySyncClaimsSchema = z
|
|
4060
|
-
purpose: z
|
|
4061
|
-
organizationId: z
|
|
4062
|
-
actorId: z
|
|
4063
|
-
setupOperationId: z
|
|
4449
|
+
const DIRECTORY_SYNC_CREDENTIAL_LIFETIME_MS = 31536e6;
|
|
4450
|
+
const directorySyncClaimsSchema = z.object({
|
|
4451
|
+
purpose: z.literal(DIRECTORY_SYNC_PURPOSE),
|
|
4452
|
+
organizationId: z.string().trim().min(1),
|
|
4453
|
+
actorId: z.string().trim().min(1),
|
|
4454
|
+
setupOperationId: z.string().trim().min(16).max(255).optional()
|
|
4064
4455
|
});
|
|
4065
4456
|
function getScimEndpoint$1(baseUrl) {
|
|
4066
4457
|
return `${baseUrl}/scim/v2`;
|
|
@@ -4326,14 +4717,6 @@ async function recoverManagedDirectoryConnection(ctx, row, input, policy) {
|
|
|
4326
4717
|
async function createManagedDirectoryConnection(ctx, input, policy) {
|
|
4327
4718
|
await assertManagedDirectoryTransactionsConfigured(ctx);
|
|
4328
4719
|
if (input.pairing) await assertDirectorySyncSSOIntegrationConfigured(ctx);
|
|
4329
|
-
if (input.pairing?.protocol === "saml") try {
|
|
4330
|
-
await loadSAMLPolicy();
|
|
4331
|
-
} catch {
|
|
4332
|
-
throw ctx.error("NOT_IMPLEMENTED", {
|
|
4333
|
-
code: "DIRECTORY_SYNC_SAML_POLICY_UNAVAILABLE",
|
|
4334
|
-
message: "SAML pairing requires a compatible SSO package with service provider metadata policy support"
|
|
4335
|
-
});
|
|
4336
|
-
}
|
|
4337
4720
|
const aliasKey = await createAliasKey(input.organizationId, input.providerId);
|
|
4338
4721
|
const provisioningDomainId = await createProvisioningDomainId(input.organizationId, input.providerId);
|
|
4339
4722
|
const activeOrganizationKey = await createActiveOrganizationKey(input.organizationId);
|
|
@@ -4772,10 +5155,10 @@ const revokeDirectoryCredential = (options) => createAuthEndpoint("/dash/organiz
|
|
|
4772
5155
|
const DIRECTORY_EVENTS_DEFAULT_LIMIT = 10;
|
|
4773
5156
|
const DIRECTORY_PAGE_MAX_LIMIT = 100;
|
|
4774
5157
|
const DIRECTORY_EVENTS_DEFAULT_SORT_DIRECTION = "desc";
|
|
4775
|
-
const directoryEventsQuerySchema = z
|
|
4776
|
-
limit: z
|
|
4777
|
-
offset: z
|
|
4778
|
-
sortDirection: z
|
|
5158
|
+
const directoryEventsQuerySchema = z.object({
|
|
5159
|
+
limit: z.number().or(z.string().transform(Number)).optional(),
|
|
5160
|
+
offset: z.number().or(z.string().transform(Number)).optional(),
|
|
5161
|
+
sortDirection: z.enum(["asc", "desc"]).optional()
|
|
4779
5162
|
}).optional();
|
|
4780
5163
|
function resolveDirectoryEventsPage(query) {
|
|
4781
5164
|
const requestedLimit = query?.limit;
|
|
@@ -4927,7 +5310,7 @@ function parseMemberRoles(role) {
|
|
|
4927
5310
|
}
|
|
4928
5311
|
function getScimManagementRoles(ctx) {
|
|
4929
5312
|
const creatorRole = ctx.context.getPlugin("organization")?.options?.creatorRole ?? "owner";
|
|
4930
|
-
return Array.from(new Set(["admin", creatorRole]));
|
|
5313
|
+
return Array.from(/* @__PURE__ */ new Set(["admin", creatorRole]));
|
|
4931
5314
|
}
|
|
4932
5315
|
async function findOrganizationScimManagerUserId(ctx, organizationId) {
|
|
4933
5316
|
const requiredRoles = getScimManagementRoles(ctx);
|
|
@@ -5028,10 +5411,10 @@ const listOrganizationDirectories = (options) => {
|
|
|
5028
5411
|
const createOrganizationDirectoryLegacy = (options) => {
|
|
5029
5412
|
return createAuthEndpoint("/dash/organization/directory/create", {
|
|
5030
5413
|
method: "POST",
|
|
5031
|
-
use: [jwtMiddleware(options, z
|
|
5032
|
-
body: z
|
|
5033
|
-
providerId: z
|
|
5034
|
-
ownerUserId: z
|
|
5414
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
5415
|
+
body: z.object({
|
|
5416
|
+
providerId: z.string().min(1, "Provider ID is required"),
|
|
5417
|
+
ownerUserId: z.string().min(1, "Owner user ID is required")
|
|
5035
5418
|
})
|
|
5036
5419
|
}, async (ctx) => {
|
|
5037
5420
|
requireOrganizationPlugin(ctx);
|
|
@@ -5068,8 +5451,8 @@ const createOrganizationDirectoryLegacy = (options) => {
|
|
|
5068
5451
|
const deleteOrganizationDirectoryLegacy = (options) => {
|
|
5069
5452
|
return createAuthEndpoint("/dash/organization/directory/delete", {
|
|
5070
5453
|
method: "POST",
|
|
5071
|
-
use: [jwtMiddleware(options, z
|
|
5072
|
-
body: z
|
|
5454
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
5455
|
+
body: z.object({ providerId: z.string().min(1, "Provider ID is required") })
|
|
5073
5456
|
}, async (ctx) => {
|
|
5074
5457
|
requireOrganizationPlugin(ctx);
|
|
5075
5458
|
const scimPlugin = getSCIMPlugin(ctx);
|
|
@@ -5092,8 +5475,8 @@ const deleteOrganizationDirectoryLegacy = (options) => {
|
|
|
5092
5475
|
const regenerateDirectoryTokenLegacy = (options) => {
|
|
5093
5476
|
return createAuthEndpoint("/dash/organization/directory/regenerate-token", {
|
|
5094
5477
|
method: "POST",
|
|
5095
|
-
use: [jwtMiddleware(options, z
|
|
5096
|
-
body: z
|
|
5478
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
5479
|
+
body: z.object({ providerId: z.string().min(1, "Provider ID is required") })
|
|
5097
5480
|
}, async (ctx) => {
|
|
5098
5481
|
requireOrganizationPlugin(ctx);
|
|
5099
5482
|
const scimPlugin = getSCIMPlugin(ctx);
|
|
@@ -5163,13 +5546,13 @@ const getUserEvents = (options) => {
|
|
|
5163
5546
|
return createAuthEndpoint("/events/list", {
|
|
5164
5547
|
method: "GET",
|
|
5165
5548
|
use: [sessionMiddleware],
|
|
5166
|
-
query: z
|
|
5549
|
+
query: z.object({
|
|
5167
5550
|
/** Maximum number of events to return (default: 50, max: 100) */
|
|
5168
|
-
limit: z
|
|
5551
|
+
limit: z.number().or(z.string().transform(Number)).optional(),
|
|
5169
5552
|
/** Number of events to skip for pagination (default: 0) */
|
|
5170
|
-
offset: z
|
|
5553
|
+
offset: z.number().or(z.string().transform(Number)).optional(),
|
|
5171
5554
|
/** Filter by event type (e.g., "user_signed_in") */
|
|
5172
|
-
eventType: z
|
|
5555
|
+
eventType: z.string().optional()
|
|
5173
5556
|
}).optional()
|
|
5174
5557
|
}, async (ctx) => {
|
|
5175
5558
|
const session = ctx.context.session;
|
|
@@ -5232,13 +5615,13 @@ const getAuditLogs = (options) => {
|
|
|
5232
5615
|
return createAuthEndpoint("/events/audit-logs", {
|
|
5233
5616
|
method: "GET",
|
|
5234
5617
|
use: [sessionMiddleware],
|
|
5235
|
-
query: z
|
|
5236
|
-
limit: z
|
|
5237
|
-
offset: z
|
|
5238
|
-
userId: z
|
|
5239
|
-
organizationId: z
|
|
5240
|
-
identifier: z
|
|
5241
|
-
eventType: z
|
|
5618
|
+
query: z.object({
|
|
5619
|
+
limit: z.number().or(z.string().transform(Number)).optional(),
|
|
5620
|
+
offset: z.number().or(z.string().transform(Number)).optional(),
|
|
5621
|
+
userId: z.string().optional(),
|
|
5622
|
+
organizationId: z.string().optional(),
|
|
5623
|
+
identifier: z.string().optional(),
|
|
5624
|
+
eventType: z.string().optional()
|
|
5242
5625
|
}).optional()
|
|
5243
5626
|
}, async (ctx) => {
|
|
5244
5627
|
const session = ctx.context.session;
|
|
@@ -5313,7 +5696,7 @@ const getAuditLogs = (options) => {
|
|
|
5313
5696
|
};
|
|
5314
5697
|
});
|
|
5315
5698
|
};
|
|
5316
|
-
const OWNER_ADMIN_ROLES = new Set(["owner", "admin"]);
|
|
5699
|
+
const OWNER_ADMIN_ROLES = /* @__PURE__ */ new Set(["owner", "admin"]);
|
|
5317
5700
|
function isOwnerOrAdminRole(role) {
|
|
5318
5701
|
return role !== void 0 && OWNER_ADMIN_ROLES.has(role);
|
|
5319
5702
|
}
|
|
@@ -5354,15 +5737,15 @@ const getAllAuditLogs = (options) => {
|
|
|
5354
5737
|
return createAuthEndpoint("/events/all-audit-logs", {
|
|
5355
5738
|
method: "GET",
|
|
5356
5739
|
use: [sessionMiddleware],
|
|
5357
|
-
query: z
|
|
5358
|
-
limit: z
|
|
5359
|
-
offset: z
|
|
5360
|
-
userId: z
|
|
5361
|
-
organizationId: z
|
|
5740
|
+
query: z.object({
|
|
5741
|
+
limit: z.number().or(z.string().transform(Number)).optional(),
|
|
5742
|
+
offset: z.number().or(z.string().transform(Number)).optional(),
|
|
5743
|
+
userId: z.string().optional(),
|
|
5744
|
+
organizationId: z.string().optional(),
|
|
5362
5745
|
/** Filter by event type (e.g. `organization_member_added`) */
|
|
5363
|
-
eventType: z
|
|
5746
|
+
eventType: z.string().optional(),
|
|
5364
5747
|
/** Match `eventData.identifier` (organization-scoped actor identity) */
|
|
5365
|
-
identifier: z
|
|
5748
|
+
identifier: z.string().optional()
|
|
5366
5749
|
}).refine((q) => {
|
|
5367
5750
|
const u = q.userId?.trim();
|
|
5368
5751
|
const o = q.organizationId?.trim();
|
|
@@ -5419,10 +5802,10 @@ const getAllAuditLogs = (options) => {
|
|
|
5419
5802
|
};
|
|
5420
5803
|
//#endregion
|
|
5421
5804
|
//#region src/routes/execute-adapter/index.ts
|
|
5422
|
-
const whereClause = z.object({
|
|
5423
|
-
field: z.string(),
|
|
5424
|
-
value: z.unknown(),
|
|
5425
|
-
operator: z.enum([
|
|
5805
|
+
const whereClause = z$1.object({
|
|
5806
|
+
field: z$1.string(),
|
|
5807
|
+
value: z$1.unknown(),
|
|
5808
|
+
operator: z$1.enum([
|
|
5426
5809
|
"eq",
|
|
5427
5810
|
"ne",
|
|
5428
5811
|
"gt",
|
|
@@ -5434,44 +5817,44 @@ const whereClause = z.object({
|
|
|
5434
5817
|
"starts_with",
|
|
5435
5818
|
"ends_with"
|
|
5436
5819
|
]).optional(),
|
|
5437
|
-
connector: z.enum(["AND", "OR"]).optional()
|
|
5820
|
+
connector: z$1.enum(["AND", "OR"]).optional()
|
|
5438
5821
|
});
|
|
5439
|
-
const sortBySchema = z.object({
|
|
5440
|
-
field: z.string(),
|
|
5441
|
-
direction: z.enum(["asc", "desc"])
|
|
5822
|
+
const sortBySchema = z$1.object({
|
|
5823
|
+
field: z$1.string(),
|
|
5824
|
+
direction: z$1.enum(["asc", "desc"])
|
|
5442
5825
|
});
|
|
5443
|
-
const actionSchema = z.discriminatedUnion("action", [
|
|
5444
|
-
z.object({
|
|
5445
|
-
action: z.literal("findOne"),
|
|
5446
|
-
model: z.string(),
|
|
5447
|
-
where: z.array(whereClause).optional(),
|
|
5448
|
-
select: z.array(z.string()).optional(),
|
|
5449
|
-
join: z.record(z.string(), z.boolean()).optional()
|
|
5826
|
+
const actionSchema = z$1.discriminatedUnion("action", [
|
|
5827
|
+
z$1.object({
|
|
5828
|
+
action: z$1.literal("findOne"),
|
|
5829
|
+
model: z$1.string(),
|
|
5830
|
+
where: z$1.array(whereClause).optional(),
|
|
5831
|
+
select: z$1.array(z$1.string()).optional(),
|
|
5832
|
+
join: z$1.record(z$1.string(), z$1.boolean()).optional()
|
|
5450
5833
|
}),
|
|
5451
|
-
z.object({
|
|
5452
|
-
action: z.literal("findMany"),
|
|
5453
|
-
model: z.string(),
|
|
5454
|
-
where: z.array(whereClause).optional(),
|
|
5455
|
-
limit: z.number().optional(),
|
|
5456
|
-
offset: z.number().optional(),
|
|
5834
|
+
z$1.object({
|
|
5835
|
+
action: z$1.literal("findMany"),
|
|
5836
|
+
model: z$1.string(),
|
|
5837
|
+
where: z$1.array(whereClause).optional(),
|
|
5838
|
+
limit: z$1.number().optional(),
|
|
5839
|
+
offset: z$1.number().optional(),
|
|
5457
5840
|
sortBy: sortBySchema.optional(),
|
|
5458
|
-
join: z.record(z.string(), z.boolean()).optional()
|
|
5841
|
+
join: z$1.record(z$1.string(), z$1.boolean()).optional()
|
|
5459
5842
|
}),
|
|
5460
|
-
z.object({
|
|
5461
|
-
action: z.literal("create"),
|
|
5462
|
-
model: z.string(),
|
|
5463
|
-
data: z.record(z.string(), z.unknown())
|
|
5843
|
+
z$1.object({
|
|
5844
|
+
action: z$1.literal("create"),
|
|
5845
|
+
model: z$1.string(),
|
|
5846
|
+
data: z$1.record(z$1.string(), z$1.unknown())
|
|
5464
5847
|
}),
|
|
5465
|
-
z.object({
|
|
5466
|
-
action: z.literal("update"),
|
|
5467
|
-
model: z.string(),
|
|
5468
|
-
where: z.array(whereClause),
|
|
5469
|
-
update: z.record(z.string(), z.unknown())
|
|
5848
|
+
z$1.object({
|
|
5849
|
+
action: z$1.literal("update"),
|
|
5850
|
+
model: z$1.string(),
|
|
5851
|
+
where: z$1.array(whereClause),
|
|
5852
|
+
update: z$1.record(z$1.string(), z$1.unknown())
|
|
5470
5853
|
}),
|
|
5471
|
-
z.object({
|
|
5472
|
-
action: z.literal("count"),
|
|
5473
|
-
model: z.string(),
|
|
5474
|
-
where: z.array(whereClause).optional()
|
|
5854
|
+
z$1.object({
|
|
5855
|
+
action: z$1.literal("count"),
|
|
5856
|
+
model: z$1.string(),
|
|
5857
|
+
where: z$1.array(whereClause).optional()
|
|
5475
5858
|
})
|
|
5476
5859
|
]);
|
|
5477
5860
|
const executeAdapter = (options) => {
|
|
@@ -5589,8 +5972,8 @@ function isSafeHttpUrl(value) {
|
|
|
5589
5972
|
}
|
|
5590
5973
|
}
|
|
5591
5974
|
/** http(s) absolute URL without embedded credentials. */
|
|
5592
|
-
const safeUrlSchema = z
|
|
5593
|
-
const optionalSafeUrlSchema = z
|
|
5975
|
+
const safeUrlSchema = z.string().trim().min(1).pipe(z.url().refine(isSafeHttpUrl, { message: "URL must be a valid http(s) URL without credentials" })).transform((value) => new URL(value).href);
|
|
5976
|
+
const optionalSafeUrlSchema = z.union([safeUrlSchema, z.literal("")]).optional();
|
|
5594
5977
|
function getAuthBaseUrl(ctx) {
|
|
5595
5978
|
const baseURL = ctx.context.options.baseURL;
|
|
5596
5979
|
return typeof baseURL === "string" && baseURL.trim() ? baseURL : "/";
|
|
@@ -5669,10 +6052,13 @@ async function executePlatformInvitationCompletion(ctx, $api, invitation, args)
|
|
|
5669
6052
|
}
|
|
5670
6053
|
});
|
|
5671
6054
|
if (markError) ctx.context.logger.warn("[Dash] Failed to mark invitation as accepted", markError);
|
|
5672
|
-
if (shouldCreateSessionOnInviteComplete(invitation.authMode))
|
|
5673
|
-
session
|
|
5674
|
-
|
|
5675
|
-
|
|
6055
|
+
if (shouldCreateSessionOnInviteComplete(invitation.authMode)) {
|
|
6056
|
+
const session = await ctx.context.internalAdapter.createSession(existingUser.id);
|
|
6057
|
+
await setSessionCookie(ctx, {
|
|
6058
|
+
session,
|
|
6059
|
+
user: existingUser
|
|
6060
|
+
});
|
|
6061
|
+
}
|
|
5676
6062
|
return { redirectUrl: resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthBaseUrl(ctx)) };
|
|
5677
6063
|
}
|
|
5678
6064
|
if (!password && !allowsPasswordlessInviteComplete(invitation.authMode)) throw new APIError("BAD_REQUEST", { message: "Password is required to complete this invitation." });
|
|
@@ -5697,10 +6083,13 @@ async function executePlatformInvitationCompletion(ctx, $api, invitation, args)
|
|
|
5697
6083
|
}
|
|
5698
6084
|
});
|
|
5699
6085
|
if (markError) ctx.context.logger.warn("[Dash] Failed to mark invitation as accepted", markError);
|
|
5700
|
-
if (shouldCreateSessionOnInviteComplete(invitation.authMode))
|
|
5701
|
-
session
|
|
5702
|
-
|
|
5703
|
-
|
|
6086
|
+
if (shouldCreateSessionOnInviteComplete(invitation.authMode)) {
|
|
6087
|
+
const session = await ctx.context.internalAdapter.createSession(user.id);
|
|
6088
|
+
await setSessionCookie(ctx, {
|
|
6089
|
+
session,
|
|
6090
|
+
user
|
|
6091
|
+
});
|
|
6092
|
+
}
|
|
5704
6093
|
return { redirectUrl: resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthBaseUrl(ctx)) };
|
|
5705
6094
|
}
|
|
5706
6095
|
/**
|
|
@@ -5712,7 +6101,7 @@ const acceptInvitation = (options) => {
|
|
|
5712
6101
|
const { $api } = options;
|
|
5713
6102
|
return createAuthEndpoint("/dash/accept-invitation", {
|
|
5714
6103
|
method: "GET",
|
|
5715
|
-
query: z
|
|
6104
|
+
query: z.object({ token: z.string() })
|
|
5716
6105
|
}, async (ctx) => {
|
|
5717
6106
|
const { token } = ctx.query;
|
|
5718
6107
|
const invitation = await verifyPendingInvitation(token, $api, ctx);
|
|
@@ -5727,10 +6116,13 @@ const acceptInvitation = (options) => {
|
|
|
5727
6116
|
}
|
|
5728
6117
|
});
|
|
5729
6118
|
if (markError) ctx.context.logger.warn("[Dash] Failed to mark invitation as accepted (existing user)", markError);
|
|
5730
|
-
if (shouldCreateSessionOnInviteComplete(invitation.authMode))
|
|
5731
|
-
session
|
|
5732
|
-
|
|
5733
|
-
|
|
6119
|
+
if (shouldCreateSessionOnInviteComplete(invitation.authMode)) {
|
|
6120
|
+
const session = await ctx.context.internalAdapter.createSession(existingUser.id);
|
|
6121
|
+
await setSessionCookie(ctx, {
|
|
6122
|
+
session,
|
|
6123
|
+
user: existingUser
|
|
6124
|
+
});
|
|
6125
|
+
}
|
|
5734
6126
|
const redirectUrl = resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthBaseUrl(ctx));
|
|
5735
6127
|
return ctx.redirect(redirectUrl);
|
|
5736
6128
|
}
|
|
@@ -5759,10 +6151,13 @@ const acceptInvitation = (options) => {
|
|
|
5759
6151
|
}
|
|
5760
6152
|
});
|
|
5761
6153
|
if (markError) ctx.context.logger.warn("[Dash] Failed to mark invitation as accepted", markError);
|
|
5762
|
-
if (shouldCreateSessionOnInviteComplete(invitation.authMode))
|
|
5763
|
-
session
|
|
5764
|
-
|
|
5765
|
-
|
|
6154
|
+
if (shouldCreateSessionOnInviteComplete(invitation.authMode)) {
|
|
6155
|
+
const session = await ctx.context.internalAdapter.createSession(user.id);
|
|
6156
|
+
await setSessionCookie(ctx, {
|
|
6157
|
+
session,
|
|
6158
|
+
user
|
|
6159
|
+
});
|
|
6160
|
+
}
|
|
5766
6161
|
const redirectUrl = resolveTrustedAuthRedirectUrl(ctx, invitation.redirectUrl, getAuthBaseUrl(ctx));
|
|
5767
6162
|
return ctx.redirect(redirectUrl);
|
|
5768
6163
|
});
|
|
@@ -5776,13 +6171,14 @@ const completeInvitation = (options) => {
|
|
|
5776
6171
|
const { $api } = options;
|
|
5777
6172
|
return createAuthEndpoint("/dash/complete-invitation", {
|
|
5778
6173
|
method: "POST",
|
|
5779
|
-
body: z
|
|
5780
|
-
token: z
|
|
5781
|
-
password: z
|
|
6174
|
+
body: z.object({
|
|
6175
|
+
token: z.string(),
|
|
6176
|
+
password: z.string().optional()
|
|
5782
6177
|
})
|
|
5783
6178
|
}, async (ctx) => {
|
|
5784
6179
|
const { token, password } = ctx.body;
|
|
5785
|
-
const
|
|
6180
|
+
const invitation = await verifyPendingInvitation(token, $api, ctx);
|
|
6181
|
+
const { redirectUrl } = await executePlatformInvitationCompletion(ctx, $api, invitation, {
|
|
5786
6182
|
token,
|
|
5787
6183
|
password
|
|
5788
6184
|
});
|
|
@@ -5800,14 +6196,15 @@ const completeInvitationHandoff = (options) => {
|
|
|
5800
6196
|
const { $api } = options;
|
|
5801
6197
|
return createAuthEndpoint("/dash/complete-invitation-handoff", {
|
|
5802
6198
|
method: "GET",
|
|
5803
|
-
query: z
|
|
6199
|
+
query: z.object({ handoff: z.string().min(1) })
|
|
5804
6200
|
}, async (ctx) => {
|
|
5805
6201
|
const { data, error } = await $api("/api/internal/invitations/redeem-handoff", {
|
|
5806
6202
|
method: "POST",
|
|
5807
6203
|
body: { handoff: ctx.query.handoff }
|
|
5808
6204
|
});
|
|
5809
6205
|
if (error || !data?.invitationToken) throw new APIError("BAD_REQUEST", { message: "This invitation link has expired. Please try again." });
|
|
5810
|
-
const
|
|
6206
|
+
const invitation = await verifyPendingInvitation(data.invitationToken, $api, ctx);
|
|
6207
|
+
const { redirectUrl } = await executePlatformInvitationCompletion(ctx, $api, invitation, {
|
|
5811
6208
|
token: data.invitationToken,
|
|
5812
6209
|
password: data.password ?? void 0
|
|
5813
6210
|
});
|
|
@@ -5818,7 +6215,7 @@ const completeInvitationSocial = (options) => {
|
|
|
5818
6215
|
const { $api } = options;
|
|
5819
6216
|
return createAuthEndpoint("/dash/complete-invitation-social", {
|
|
5820
6217
|
method: "GET",
|
|
5821
|
-
query: z
|
|
6218
|
+
query: z.object({ token: z.string() }),
|
|
5822
6219
|
use: [sessionMiddleware]
|
|
5823
6220
|
}, async (ctx) => {
|
|
5824
6221
|
const sessionUser = ctx.context.session?.user;
|
|
@@ -5853,7 +6250,7 @@ const checkUserExists = (options) => {
|
|
|
5853
6250
|
return createAuthEndpoint("/dash/check-user-exists", {
|
|
5854
6251
|
method: "POST",
|
|
5855
6252
|
use: [jwtMiddleware(options)],
|
|
5856
|
-
body: z
|
|
6253
|
+
body: z.object({ email: z.email() })
|
|
5857
6254
|
}, async (ctx) => {
|
|
5858
6255
|
const { email } = ctx.body;
|
|
5859
6256
|
const normalizedEmail = normalizeEmail(email, ctx.context);
|
|
@@ -5883,7 +6280,7 @@ function getOrganizationRoleKeys(orgOptions) {
|
|
|
5883
6280
|
if (roles && typeof roles === "object" && Object.keys(roles).length > 0) return Object.keys(roles);
|
|
5884
6281
|
return [...DEFAULT_ORG_MEMBER_ROLES];
|
|
5885
6282
|
}
|
|
5886
|
-
const organizationMemberRoleInputSchema = z
|
|
6283
|
+
const organizationMemberRoleInputSchema = z.string().trim().min(1, "Role is required").max(64, "Role is too long").regex(/^[a-zA-Z][a-zA-Z0-9_-]*$/, "Role must start with a letter and contain only letters, numbers, hyphens, and underscores");
|
|
5887
6284
|
function validateOrganizationMemberRole(ctx, role, orgOptions) {
|
|
5888
6285
|
const allowedRoles = getOrganizationRoleKeys(orgOptions);
|
|
5889
6286
|
if (!allowedRoles.includes(role)) throw ctx.error("BAD_REQUEST", { message: `Invalid role. Allowed roles: ${allowedRoles.join(", ")}` });
|
|
@@ -5945,14 +6342,14 @@ const listOrganizationInvitations = (options) => {
|
|
|
5945
6342
|
const inviteMember = (options) => {
|
|
5946
6343
|
return createAuthEndpoint("/dash/organization/invite-member", {
|
|
5947
6344
|
method: "POST",
|
|
5948
|
-
body: z
|
|
5949
|
-
email: z
|
|
6345
|
+
body: z.object({
|
|
6346
|
+
email: z.string(),
|
|
5950
6347
|
role: organizationMemberRoleInputSchema,
|
|
5951
|
-
invitedBy: z
|
|
6348
|
+
invitedBy: z.string()
|
|
5952
6349
|
}),
|
|
5953
|
-
use: [jwtMiddleware(options, z
|
|
5954
|
-
organizationId: z
|
|
5955
|
-
invitedBy: z
|
|
6350
|
+
use: [jwtMiddleware(options, z.object({
|
|
6351
|
+
organizationId: z.string(),
|
|
6352
|
+
invitedBy: z.string()
|
|
5956
6353
|
}))]
|
|
5957
6354
|
}, async (ctx) => {
|
|
5958
6355
|
const { organizationId } = ctx.context.payload;
|
|
@@ -5989,8 +6386,8 @@ const inviteMember = (options) => {
|
|
|
5989
6386
|
const checkUserByEmail = (options) => {
|
|
5990
6387
|
return createAuthEndpoint("/dash/organization/check-user-by-email", {
|
|
5991
6388
|
method: "POST",
|
|
5992
|
-
body: z
|
|
5993
|
-
use: [jwtMiddleware(options, z
|
|
6389
|
+
body: z.object({ email: z.string() }),
|
|
6390
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))]
|
|
5994
6391
|
}, async (ctx) => {
|
|
5995
6392
|
requireOrganizationPlugin(ctx);
|
|
5996
6393
|
const { organizationId } = ctx.context.payload;
|
|
@@ -6033,11 +6430,11 @@ const checkUserByEmail = (options) => {
|
|
|
6033
6430
|
const cancelInvitation = (options) => {
|
|
6034
6431
|
return createAuthEndpoint("/dash/organization/cancel-invitation", {
|
|
6035
6432
|
method: "POST",
|
|
6036
|
-
use: [jwtMiddleware(options, z
|
|
6037
|
-
organizationId: z
|
|
6038
|
-
invitationId: z
|
|
6433
|
+
use: [jwtMiddleware(options, z.object({
|
|
6434
|
+
organizationId: z.string(),
|
|
6435
|
+
invitationId: z.string()
|
|
6039
6436
|
}))],
|
|
6040
|
-
body: z
|
|
6437
|
+
body: z.object({ invitationId: z.string() })
|
|
6041
6438
|
}, async (ctx) => {
|
|
6042
6439
|
const orgOptions = requireOrganizationPlugin(ctx).options || {};
|
|
6043
6440
|
const { invitationId, organizationId } = ctx.context.payload;
|
|
@@ -6091,11 +6488,11 @@ const cancelInvitation = (options) => {
|
|
|
6091
6488
|
const resendInvitation = (options) => {
|
|
6092
6489
|
return createAuthEndpoint("/dash/organization/resend-invitation", {
|
|
6093
6490
|
method: "POST",
|
|
6094
|
-
use: [jwtMiddleware(options, z
|
|
6095
|
-
organizationId: z
|
|
6096
|
-
invitationId: z
|
|
6491
|
+
use: [jwtMiddleware(options, z.object({
|
|
6492
|
+
organizationId: z.string(),
|
|
6493
|
+
invitationId: z.string()
|
|
6097
6494
|
}))],
|
|
6098
|
-
body: z
|
|
6495
|
+
body: z.object({ invitationId: z.string() })
|
|
6099
6496
|
}, async (ctx) => {
|
|
6100
6497
|
const organizationPlugin = requireOrganizationPlugin(ctx);
|
|
6101
6498
|
const { invitationId, organizationId } = ctx.context.payload;
|
|
@@ -6204,9 +6601,9 @@ const listOrganizationMembers = (options) => {
|
|
|
6204
6601
|
const addMember = (options) => {
|
|
6205
6602
|
return createAuthEndpoint("/dash/organization/add-member", {
|
|
6206
6603
|
method: "POST",
|
|
6207
|
-
use: [jwtMiddleware(options, z
|
|
6208
|
-
body: z
|
|
6209
|
-
userId: z
|
|
6604
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
6605
|
+
body: z.object({
|
|
6606
|
+
userId: z.string(),
|
|
6210
6607
|
role: organizationMemberRoleInputSchema
|
|
6211
6608
|
})
|
|
6212
6609
|
}, async (ctx) => {
|
|
@@ -6261,8 +6658,8 @@ const addMember = (options) => {
|
|
|
6261
6658
|
const removeMember = (options) => {
|
|
6262
6659
|
return createAuthEndpoint("/dash/organization/remove-member", {
|
|
6263
6660
|
method: "POST",
|
|
6264
|
-
use: [jwtMiddleware(options, z
|
|
6265
|
-
body: z
|
|
6661
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
6662
|
+
body: z.object({ memberId: z.string() })
|
|
6266
6663
|
}, async (ctx) => {
|
|
6267
6664
|
const { organizationId } = ctx.context.payload;
|
|
6268
6665
|
const orgOptions = requireOrganizationPlugin(ctx).options || {};
|
|
@@ -6331,9 +6728,9 @@ const removeMember = (options) => {
|
|
|
6331
6728
|
const updateMemberRole = (options) => {
|
|
6332
6729
|
return createAuthEndpoint("/dash/organization/update-member-role", {
|
|
6333
6730
|
method: "POST",
|
|
6334
|
-
use: [jwtMiddleware(options, z
|
|
6335
|
-
body: z
|
|
6336
|
-
memberId: z
|
|
6731
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
6732
|
+
body: z.object({
|
|
6733
|
+
memberId: z.string(),
|
|
6337
6734
|
role: organizationMemberRoleInputSchema
|
|
6338
6735
|
})
|
|
6339
6736
|
}, async (ctx) => {
|
|
@@ -6392,7 +6789,7 @@ const updateMemberRole = (options) => {
|
|
|
6392
6789
|
//#region src/export-factory.ts
|
|
6393
6790
|
const exportFactory = (input, options) => async (ctx) => {
|
|
6394
6791
|
const batchSize = options?.batchSize || 1e4;
|
|
6395
|
-
const staleMs = options?.staleMs ||
|
|
6792
|
+
const staleMs = options?.staleMs || 3e5;
|
|
6396
6793
|
const enabledFields = options?.enabledFields || [];
|
|
6397
6794
|
const userLimit = input.limit;
|
|
6398
6795
|
const userOffset = input.offset || 0;
|
|
@@ -6489,16 +6886,16 @@ async function withConcurrency(items, fn, options) {
|
|
|
6489
6886
|
return results;
|
|
6490
6887
|
}
|
|
6491
6888
|
/** Zod schema for validating organization/project slug format. */
|
|
6492
|
-
const slugSchema = z.string().min(1, "Slug is required").regex(/^[a-z0-9-]+$/, "Slug can only contain lowercase letters, numbers, and hyphens");
|
|
6889
|
+
const slugSchema = z$1.string().min(1, "Slug is required").regex(/^[a-z0-9-]+$/, "Slug can only contain lowercase letters, numbers, and hyphens");
|
|
6493
6890
|
//#endregion
|
|
6494
6891
|
//#region src/routes/organizations/schemas.ts
|
|
6495
|
-
const DENIED_ORG_WRITE_KEYS = new Set([
|
|
6892
|
+
const DENIED_ORG_WRITE_KEYS = /* @__PURE__ */ new Set([
|
|
6496
6893
|
"id",
|
|
6497
6894
|
"createdAt",
|
|
6498
6895
|
"updatedAt"
|
|
6499
6896
|
]);
|
|
6500
|
-
const CREATE_NON_PERSISTED_KEYS = new Set(["defaultTeamName"]);
|
|
6501
|
-
const CREATE_REQUEST_ONLY_KEYS$1 = new Set(["userId", "skipDefaultTeam"]);
|
|
6897
|
+
const CREATE_NON_PERSISTED_KEYS = /* @__PURE__ */ new Set(["defaultTeamName"]);
|
|
6898
|
+
const CREATE_REQUEST_ONLY_KEYS$1 = /* @__PURE__ */ new Set(["userId", "skipDefaultTeam"]);
|
|
6502
6899
|
const CORE_CREATE_KEYS$1 = [
|
|
6503
6900
|
"name",
|
|
6504
6901
|
"slug",
|
|
@@ -6530,7 +6927,7 @@ function getWritableOrganizationFieldNames(orgOptions, mode) {
|
|
|
6530
6927
|
*/
|
|
6531
6928
|
function pickWritableOrganizationFields(body, orgOptions, mode) {
|
|
6532
6929
|
const allowed = getWritableOrganizationFieldNames(orgOptions, mode);
|
|
6533
|
-
const skip = new Set([
|
|
6930
|
+
const skip = /* @__PURE__ */ new Set([
|
|
6534
6931
|
...DENIED_ORG_WRITE_KEYS,
|
|
6535
6932
|
...mode === "create" ? CREATE_NON_PERSISTED_KEYS : [],
|
|
6536
6933
|
...mode === "create" ? CREATE_REQUEST_ONLY_KEYS$1 : []
|
|
@@ -6542,26 +6939,26 @@ function pickWritableOrganizationFields(body, orgOptions, mode) {
|
|
|
6542
6939
|
}
|
|
6543
6940
|
return result;
|
|
6544
6941
|
}
|
|
6545
|
-
const BaseCreateOrgCoreBodySchema = z
|
|
6546
|
-
name: z
|
|
6942
|
+
const BaseCreateOrgCoreBodySchema = z.object({
|
|
6943
|
+
name: z.string(),
|
|
6547
6944
|
slug: slugSchema,
|
|
6548
|
-
logo: z
|
|
6549
|
-
defaultTeamName: z
|
|
6945
|
+
logo: z.string().optional(),
|
|
6946
|
+
defaultTeamName: z.string().optional()
|
|
6550
6947
|
});
|
|
6551
|
-
const BaseUpdateOrgCoreBodySchema = z
|
|
6948
|
+
const BaseUpdateOrgCoreBodySchema = z.object({
|
|
6552
6949
|
logo: optionalSafeUrlSchema,
|
|
6553
|
-
name: z
|
|
6950
|
+
name: z.string().optional(),
|
|
6554
6951
|
slug: slugSchema.optional(),
|
|
6555
|
-
metadata: z
|
|
6952
|
+
metadata: z.string().optional()
|
|
6556
6953
|
});
|
|
6557
|
-
const CreateOrganizationBodySchema = BaseCreateOrgCoreBodySchema.catchall(z
|
|
6558
|
-
const UpdateOrganizationBodySchema = BaseUpdateOrgCoreBodySchema.catchall(z
|
|
6954
|
+
const CreateOrganizationBodySchema = BaseCreateOrgCoreBodySchema.catchall(z.unknown());
|
|
6955
|
+
const UpdateOrganizationBodySchema = BaseUpdateOrgCoreBodySchema.catchall(z.unknown());
|
|
6559
6956
|
function createSchemaForDBField$1(field) {
|
|
6560
6957
|
switch (field.type) {
|
|
6561
|
-
case "number": return field.required ? z
|
|
6562
|
-
case "boolean": return field.required ? z
|
|
6563
|
-
case "date": return field.required ? z
|
|
6564
|
-
default: return field.required ? z
|
|
6958
|
+
case "number": return field.required ? z.coerce.number() : z.coerce.number().optional();
|
|
6959
|
+
case "boolean": return field.required ? z.coerce.boolean() : z.coerce.boolean().optional();
|
|
6960
|
+
case "date": return field.required ? z.union([z.string().min(1), z.coerce.date()]) : z.union([z.string(), z.coerce.date()]).optional();
|
|
6961
|
+
default: return field.required ? z.string().min(1) : z.string().optional();
|
|
6565
6962
|
}
|
|
6566
6963
|
}
|
|
6567
6964
|
/**
|
|
@@ -6570,39 +6967,39 @@ function createSchemaForDBField$1(field) {
|
|
|
6570
6967
|
*/
|
|
6571
6968
|
function validateWritableCreateOrganizationFields(data, options) {
|
|
6572
6969
|
const shape = {
|
|
6573
|
-
name: z
|
|
6970
|
+
name: z.string().min(1),
|
|
6574
6971
|
slug: slugSchema,
|
|
6575
|
-
logo: z
|
|
6972
|
+
logo: z.string().optional()
|
|
6576
6973
|
};
|
|
6577
6974
|
const additional = getOrganizationAdditionalFields(options);
|
|
6578
6975
|
for (const [name, field] of Object.entries(additional)) {
|
|
6579
6976
|
if (field.input === false) continue;
|
|
6580
6977
|
shape[name] = createSchemaForDBField$1(field);
|
|
6581
6978
|
}
|
|
6582
|
-
const result = z
|
|
6979
|
+
const result = z.object(shape).strict().safeParse(data);
|
|
6583
6980
|
if (!result.success) throw result.error;
|
|
6584
6981
|
}
|
|
6585
6982
|
/**
|
|
6586
6983
|
* Ensures at least one field is present on update and validates additional field types.
|
|
6587
6984
|
*/
|
|
6588
6985
|
function validateWritableOrganizationUpdateFields(data, orgOptions) {
|
|
6589
|
-
if (Object.keys(data).length === 0) throw new z
|
|
6986
|
+
if (Object.keys(data).length === 0) throw new z.ZodError([{
|
|
6590
6987
|
code: "custom",
|
|
6591
6988
|
message: "No valid fields to update",
|
|
6592
6989
|
path: []
|
|
6593
6990
|
}]);
|
|
6594
6991
|
const shape = {
|
|
6595
|
-
name: z
|
|
6992
|
+
name: z.string().min(1).optional(),
|
|
6596
6993
|
slug: slugSchema.optional(),
|
|
6597
6994
|
logo: optionalSafeUrlSchema,
|
|
6598
|
-
metadata: z
|
|
6995
|
+
metadata: z.string().optional()
|
|
6599
6996
|
};
|
|
6600
6997
|
const additional = getOrganizationAdditionalFields(orgOptions);
|
|
6601
6998
|
for (const [name, field] of Object.entries(additional)) {
|
|
6602
6999
|
if (field.input === false) continue;
|
|
6603
7000
|
shape[name] = createSchemaForDBField$1(field);
|
|
6604
7001
|
}
|
|
6605
|
-
const result = z
|
|
7002
|
+
const result = z.object(shape).partial().strict().safeParse(data);
|
|
6606
7003
|
if (!result.success) throw result.error;
|
|
6607
7004
|
}
|
|
6608
7005
|
//#endregion
|
|
@@ -6644,26 +7041,26 @@ const listOrganizations = (options) => {
|
|
|
6644
7041
|
return createAuthEndpoint("/dash/list-organizations", {
|
|
6645
7042
|
method: "GET",
|
|
6646
7043
|
use: [jwtMiddleware(options)],
|
|
6647
|
-
query: z
|
|
6648
|
-
limit: z
|
|
6649
|
-
offset: z
|
|
6650
|
-
sortBy: z
|
|
7044
|
+
query: z.object({
|
|
7045
|
+
limit: z.number().or(z.string().transform(Number)).optional(),
|
|
7046
|
+
offset: z.number().or(z.string().transform(Number)).optional(),
|
|
7047
|
+
sortBy: z.enum([
|
|
6651
7048
|
"createdAt",
|
|
6652
7049
|
"name",
|
|
6653
7050
|
"slug",
|
|
6654
7051
|
"members"
|
|
6655
7052
|
]).optional(),
|
|
6656
|
-
sortOrder: z
|
|
6657
|
-
filterMembers: z
|
|
7053
|
+
sortOrder: z.enum(["asc", "desc"]).optional(),
|
|
7054
|
+
filterMembers: z.enum([
|
|
6658
7055
|
"abandoned",
|
|
6659
7056
|
"eq1",
|
|
6660
7057
|
"gt1",
|
|
6661
7058
|
"gt5",
|
|
6662
7059
|
"gt10"
|
|
6663
7060
|
]).optional(),
|
|
6664
|
-
search: z
|
|
6665
|
-
startDate: z
|
|
6666
|
-
endDate: z
|
|
7061
|
+
search: z.string().optional(),
|
|
7062
|
+
startDate: z.date().or(z.string().transform((val) => new Date(val))).optional(),
|
|
7063
|
+
endDate: z.date().or(z.string().transform((val) => new Date(val))).optional()
|
|
6667
7064
|
}).optional()
|
|
6668
7065
|
}, async (ctx) => {
|
|
6669
7066
|
const { limit = 10, offset = 0, sortBy = "createdAt", sortOrder = "desc", search, filterMembers } = ctx.query || {};
|
|
@@ -6784,12 +7181,12 @@ function parseWhereClause$1(val) {
|
|
|
6784
7181
|
if (!Array.isArray(parsed)) return [];
|
|
6785
7182
|
return parsed;
|
|
6786
7183
|
}
|
|
6787
|
-
const exportOrganizationsQuerySchema = z
|
|
6788
|
-
limit: z
|
|
6789
|
-
offset: z
|
|
6790
|
-
sortBy: z
|
|
6791
|
-
sortOrder: z
|
|
6792
|
-
where: z
|
|
7184
|
+
const exportOrganizationsQuerySchema = z.object({
|
|
7185
|
+
limit: z.number().or(z.string().transform(Number)).optional(),
|
|
7186
|
+
offset: z.number().or(z.string().transform(Number)).optional(),
|
|
7187
|
+
sortBy: z.string().optional(),
|
|
7188
|
+
sortOrder: z.enum(["asc", "desc"]).optional(),
|
|
7189
|
+
where: z.string().transform(parseWhereClause$1).optional()
|
|
6793
7190
|
}).optional();
|
|
6794
7191
|
const exportOrganizations = (options) => {
|
|
6795
7192
|
return createAuthEndpoint("/dash/export-organizations", {
|
|
@@ -6854,8 +7251,8 @@ const getOrganization = (options) => {
|
|
|
6854
7251
|
const deleteOrganization = (options) => {
|
|
6855
7252
|
return createAuthEndpoint("/dash/organization/delete", {
|
|
6856
7253
|
method: "POST",
|
|
6857
|
-
use: [jwtMiddleware(options, z
|
|
6858
|
-
body: z
|
|
7254
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
7255
|
+
body: z.object({ organizationId: z.string() })
|
|
6859
7256
|
}, async (ctx) => {
|
|
6860
7257
|
const { organizationId } = ctx.context.payload;
|
|
6861
7258
|
const { organizationId: bodyOrganizationId } = ctx.body;
|
|
@@ -6904,7 +7301,7 @@ const deleteOrganization = (options) => {
|
|
|
6904
7301
|
const deleteManyOrganizations = (options) => {
|
|
6905
7302
|
return createAuthEndpoint("/dash/organization/delete-many", {
|
|
6906
7303
|
method: "POST",
|
|
6907
|
-
use: [jwtMiddleware(options, z
|
|
7304
|
+
use: [jwtMiddleware(options, z.object({ organizationIds: z.string().array() }))]
|
|
6908
7305
|
}, async (ctx) => {
|
|
6909
7306
|
requireOrganizationPlugin(ctx);
|
|
6910
7307
|
const { organizationIds } = ctx.context.payload;
|
|
@@ -6941,9 +7338,9 @@ const deleteManyOrganizations = (options) => {
|
|
|
6941
7338
|
const createOrganization = (options) => {
|
|
6942
7339
|
return createAuthEndpoint("/dash/organization/create", {
|
|
6943
7340
|
method: "POST",
|
|
6944
|
-
use: [jwtMiddleware(options, z
|
|
6945
|
-
userId: z
|
|
6946
|
-
skipDefaultTeam: z
|
|
7341
|
+
use: [jwtMiddleware(options, z.object({
|
|
7342
|
+
userId: z.string(),
|
|
7343
|
+
skipDefaultTeam: z.boolean().optional().default(false)
|
|
6947
7344
|
}))],
|
|
6948
7345
|
body: CreateOrganizationBodySchema
|
|
6949
7346
|
}, async (ctx) => {
|
|
@@ -6969,7 +7366,7 @@ const createOrganization = (options) => {
|
|
|
6969
7366
|
try {
|
|
6970
7367
|
validateWritableCreateOrganizationFields(orgData, orgOptions);
|
|
6971
7368
|
} catch (error) {
|
|
6972
|
-
if (error instanceof z
|
|
7369
|
+
if (error instanceof z.ZodError) throw ctx.error("BAD_REQUEST", { message: error.issues[0]?.message ?? "Invalid organization data" });
|
|
6973
7370
|
throw error;
|
|
6974
7371
|
}
|
|
6975
7372
|
if (orgOptions.organizationCreation?.beforeCreate) {
|
|
@@ -7105,7 +7502,7 @@ const createOrganization = (options) => {
|
|
|
7105
7502
|
const updateOrganization = (options) => {
|
|
7106
7503
|
return createAuthEndpoint("/dash/organization/update", {
|
|
7107
7504
|
method: "POST",
|
|
7108
|
-
use: [jwtMiddleware(options, z
|
|
7505
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
7109
7506
|
body: UpdateOrganizationBodySchema
|
|
7110
7507
|
}, async (ctx) => {
|
|
7111
7508
|
const { organizationId } = ctx.context.payload;
|
|
@@ -7145,7 +7542,7 @@ const updateOrganization = (options) => {
|
|
|
7145
7542
|
try {
|
|
7146
7543
|
validateWritableOrganizationUpdateFields(updateData, orgOptions);
|
|
7147
7544
|
} catch (error) {
|
|
7148
|
-
if (error instanceof z
|
|
7545
|
+
if (error instanceof z.ZodError) throw ctx.error("BAD_REQUEST", { message: error.issues[0]?.message ?? "Invalid organization data" });
|
|
7149
7546
|
throw error;
|
|
7150
7547
|
}
|
|
7151
7548
|
if (typeof updateData.metadata === "string") try {
|
|
@@ -7232,10 +7629,10 @@ const listOrganizationTeams = (options) => {
|
|
|
7232
7629
|
const updateTeam = (options) => {
|
|
7233
7630
|
return createAuthEndpoint("/dash/organization/update-team", {
|
|
7234
7631
|
method: "POST",
|
|
7235
|
-
use: [jwtMiddleware(options, z
|
|
7236
|
-
body: z
|
|
7237
|
-
teamId: z
|
|
7238
|
-
name: z
|
|
7632
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
7633
|
+
body: z.object({
|
|
7634
|
+
teamId: z.string(),
|
|
7635
|
+
name: z.string().optional()
|
|
7239
7636
|
})
|
|
7240
7637
|
}, async (ctx) => {
|
|
7241
7638
|
const { organizationId } = ctx.context.payload;
|
|
@@ -7306,8 +7703,8 @@ const updateTeam = (options) => {
|
|
|
7306
7703
|
const deleteTeam = (options) => {
|
|
7307
7704
|
return createAuthEndpoint("/dash/organization/delete-team", {
|
|
7308
7705
|
method: "POST",
|
|
7309
|
-
use: [jwtMiddleware(options, z
|
|
7310
|
-
body: z
|
|
7706
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
7707
|
+
body: z.object({ teamId: z.string() })
|
|
7311
7708
|
}, async (ctx) => {
|
|
7312
7709
|
const { organizationId } = ctx.context.payload;
|
|
7313
7710
|
const orgOptions = requireTeamsEnabled(ctx);
|
|
@@ -7375,8 +7772,8 @@ const deleteTeam = (options) => {
|
|
|
7375
7772
|
const createTeam = (options) => {
|
|
7376
7773
|
return createAuthEndpoint("/dash/organization/create-team", {
|
|
7377
7774
|
method: "POST",
|
|
7378
|
-
use: [jwtMiddleware(options, z
|
|
7379
|
-
body: z
|
|
7775
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
7776
|
+
body: z.object({ name: z.string() })
|
|
7380
7777
|
}, async (ctx) => {
|
|
7381
7778
|
const { organizationId } = ctx.context.payload;
|
|
7382
7779
|
const orgOptions = requireTeamsEnabled(ctx);
|
|
@@ -7494,10 +7891,10 @@ const listTeamMembers = (options) => {
|
|
|
7494
7891
|
const addTeamMember = (options) => {
|
|
7495
7892
|
return createAuthEndpoint("/dash/organization/add-team-member", {
|
|
7496
7893
|
method: "POST",
|
|
7497
|
-
use: [jwtMiddleware(options, z
|
|
7498
|
-
body: z
|
|
7499
|
-
teamId: z
|
|
7500
|
-
userId: z
|
|
7894
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
7895
|
+
body: z.object({
|
|
7896
|
+
teamId: z.string(),
|
|
7897
|
+
userId: z.string()
|
|
7501
7898
|
})
|
|
7502
7899
|
}, async (ctx) => {
|
|
7503
7900
|
const { organizationId } = ctx.context.payload;
|
|
@@ -7587,10 +7984,10 @@ const addTeamMember = (options) => {
|
|
|
7587
7984
|
const removeTeamMember = (options) => {
|
|
7588
7985
|
return createAuthEndpoint("/dash/organization/remove-team-member", {
|
|
7589
7986
|
method: "POST",
|
|
7590
|
-
use: [jwtMiddleware(options, z
|
|
7591
|
-
body: z
|
|
7592
|
-
teamId: z
|
|
7593
|
-
userId: z
|
|
7987
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
7988
|
+
body: z.object({
|
|
7989
|
+
teamId: z.string(),
|
|
7990
|
+
userId: z.string()
|
|
7594
7991
|
})
|
|
7595
7992
|
}, async (ctx) => {
|
|
7596
7993
|
const { organizationId } = ctx.context.payload;
|
|
@@ -7693,7 +8090,7 @@ const revokeSession = (options) => createAuthEndpoint("/dash/sessions/revoke", {
|
|
|
7693
8090
|
const revokeAllSessions = (options) => createAuthEndpoint("/dash/sessions/revoke-all", {
|
|
7694
8091
|
method: "POST",
|
|
7695
8092
|
use: [jwtMiddleware(options)],
|
|
7696
|
-
body: z
|
|
8093
|
+
body: z.object({ userId: z.string() })
|
|
7697
8094
|
}, async (ctx) => {
|
|
7698
8095
|
const { userId } = ctx.body;
|
|
7699
8096
|
if (!await ctx.context.internalAdapter.findUserById(userId)) throw ctx.error("NOT_FOUND", { message: "User not found" });
|
|
@@ -7702,7 +8099,7 @@ const revokeAllSessions = (options) => createAuthEndpoint("/dash/sessions/revoke
|
|
|
7702
8099
|
});
|
|
7703
8100
|
const revokeManySessions = (options) => createAuthEndpoint("/dash/sessions/revoke-many", {
|
|
7704
8101
|
method: "POST",
|
|
7705
|
-
use: [jwtMiddleware(options, z
|
|
8102
|
+
use: [jwtMiddleware(options, z.object({ userIds: z.string().array() }))]
|
|
7706
8103
|
}, async (ctx) => {
|
|
7707
8104
|
const { userIds } = ctx.context.payload;
|
|
7708
8105
|
await withConcurrency(chunkArray(userIds, { batchSize: 50 }), async (chunk) => {
|
|
@@ -7747,7 +8144,7 @@ function getSSOPlugin(ctx) {
|
|
|
7747
8144
|
* (see packages/sso dist constants). Inlined so consumers (e.g. Metro) never
|
|
7748
8145
|
* statically pull @better-auth/sso for validation-only code paths.
|
|
7749
8146
|
*/
|
|
7750
|
-
const DEFAULT_MAX_SAML_METADATA_SIZE =
|
|
8147
|
+
const DEFAULT_MAX_SAML_METADATA_SIZE = 102400;
|
|
7751
8148
|
const RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
|
|
7752
8149
|
const SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1";
|
|
7753
8150
|
/** @public */
|
|
@@ -7831,44 +8228,44 @@ function requireOrganizationAccess(ctx) {
|
|
|
7831
8228
|
const orgIdFromToken = ctx.context.payload?.organizationId;
|
|
7832
8229
|
if (!orgIdFromToken || orgIdFromUrl !== orgIdFromToken) throw ctx.error("FORBIDDEN", { message: "You do not have access to this organization" });
|
|
7833
8230
|
}
|
|
7834
|
-
const samlConfigSchema = z
|
|
7835
|
-
idpMetadata: z
|
|
7836
|
-
metadata: z
|
|
7837
|
-
metadataUrl: z
|
|
8231
|
+
const samlConfigSchema = z.object({
|
|
8232
|
+
idpMetadata: z.object({
|
|
8233
|
+
metadata: z.string().optional(),
|
|
8234
|
+
metadataUrl: z.string().optional()
|
|
7838
8235
|
}).optional(),
|
|
7839
|
-
entryPoint: z
|
|
7840
|
-
cert: z
|
|
7841
|
-
entityId: z
|
|
7842
|
-
mapping: z
|
|
8236
|
+
entryPoint: z.string().optional(),
|
|
8237
|
+
cert: z.string().optional(),
|
|
8238
|
+
entityId: z.string().optional(),
|
|
8239
|
+
mapping: z.object({
|
|
7843
8240
|
/** @deprecated Removed in better-auth 1.7+; SAML subject is NameID. */
|
|
7844
|
-
id: z
|
|
7845
|
-
email: z
|
|
7846
|
-
emailVerified: z
|
|
7847
|
-
name: z
|
|
7848
|
-
firstName: z
|
|
7849
|
-
lastName: z
|
|
7850
|
-
extraFields: z
|
|
8241
|
+
id: z.string().optional(),
|
|
8242
|
+
email: z.string().optional(),
|
|
8243
|
+
emailVerified: z.string().optional(),
|
|
8244
|
+
name: z.string().optional(),
|
|
8245
|
+
firstName: z.string().optional(),
|
|
8246
|
+
lastName: z.string().optional(),
|
|
8247
|
+
extraFields: z.record(z.string(), z.any()).optional()
|
|
7851
8248
|
}).optional()
|
|
7852
8249
|
});
|
|
7853
|
-
const oidcConfigSchema = z
|
|
7854
|
-
clientId: z
|
|
7855
|
-
clientSecret: z
|
|
7856
|
-
discoveryUrl: z
|
|
7857
|
-
issuer: z
|
|
7858
|
-
discoveryEndpoint: z
|
|
7859
|
-
authorizationEndpoint: z
|
|
7860
|
-
tokenEndpoint: z
|
|
7861
|
-
jwksEndpoint: z
|
|
7862
|
-
userInfoEndpoint: z
|
|
7863
|
-
tokenEndpointAuthentication: z
|
|
7864
|
-
mapping: z
|
|
8250
|
+
const oidcConfigSchema = z.object({
|
|
8251
|
+
clientId: z.string(),
|
|
8252
|
+
clientSecret: z.string().optional(),
|
|
8253
|
+
discoveryUrl: z.string().optional(),
|
|
8254
|
+
issuer: z.string().optional(),
|
|
8255
|
+
discoveryEndpoint: z.string().optional(),
|
|
8256
|
+
authorizationEndpoint: z.string().optional(),
|
|
8257
|
+
tokenEndpoint: z.string().optional(),
|
|
8258
|
+
jwksEndpoint: z.string().optional(),
|
|
8259
|
+
userInfoEndpoint: z.string().optional(),
|
|
8260
|
+
tokenEndpointAuthentication: z.enum(["client_secret_post", "client_secret_basic"]).optional(),
|
|
8261
|
+
mapping: z.object({
|
|
7865
8262
|
/** @deprecated Removed in better-auth 1.7+; OIDC subject is `sub`. */
|
|
7866
|
-
id: z
|
|
7867
|
-
email: z
|
|
7868
|
-
emailVerified: z
|
|
7869
|
-
name: z
|
|
7870
|
-
image: z
|
|
7871
|
-
extraFields: z
|
|
8263
|
+
id: z.string().optional(),
|
|
8264
|
+
email: z.string().optional(),
|
|
8265
|
+
emailVerified: z.string().optional(),
|
|
8266
|
+
name: z.string().optional(),
|
|
8267
|
+
image: z.string().optional(),
|
|
8268
|
+
extraFields: z.record(z.string(), z.any()).optional()
|
|
7872
8269
|
}).optional()
|
|
7873
8270
|
});
|
|
7874
8271
|
async function resolveSAMLConfig(samlConfig, providerId, baseURL, ctx) {
|
|
@@ -7961,7 +8358,7 @@ async function resolveOIDCConfig(oidcConfig, _domain, ctx) {
|
|
|
7961
8358
|
const listOrganizationSsoProviders = (options) => {
|
|
7962
8359
|
return createAuthEndpoint("/dash/organization/:id/sso-providers", {
|
|
7963
8360
|
method: "GET",
|
|
7964
|
-
use: [jwtMiddleware(options, z
|
|
8361
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))]
|
|
7965
8362
|
}, async (ctx) => {
|
|
7966
8363
|
if (!isOrganizationEnabled(ctx)) {
|
|
7967
8364
|
ctx.context.logger.warn("[Dash] Organization plugin not enabled, returning empty SSO providers list");
|
|
@@ -7990,12 +8387,12 @@ const listOrganizationSsoProviders = (options) => {
|
|
|
7990
8387
|
const createSsoProvider = (options) => {
|
|
7991
8388
|
return createAuthEndpoint("/dash/organization/:id/sso-provider/create", {
|
|
7992
8389
|
method: "POST",
|
|
7993
|
-
use: [jwtMiddleware(options, z
|
|
7994
|
-
body: z
|
|
7995
|
-
providerId: z
|
|
7996
|
-
domain: z
|
|
7997
|
-
protocol: z
|
|
7998
|
-
userId: z
|
|
8390
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
8391
|
+
body: z.object({
|
|
8392
|
+
providerId: z.string(),
|
|
8393
|
+
domain: z.string(),
|
|
8394
|
+
protocol: z.enum(["SAML", "OIDC"]),
|
|
8395
|
+
userId: z.string(),
|
|
7999
8396
|
samlConfig: samlConfigSchema.optional(),
|
|
8000
8397
|
oidcConfig: oidcConfigSchema.optional()
|
|
8001
8398
|
})
|
|
@@ -8057,11 +8454,11 @@ const createSsoProvider = (options) => {
|
|
|
8057
8454
|
const updateSsoProvider = (options) => {
|
|
8058
8455
|
return createAuthEndpoint("/dash/organization/:id/sso-provider/update", {
|
|
8059
8456
|
method: "POST",
|
|
8060
|
-
use: [jwtMiddleware(options, z
|
|
8061
|
-
body: z
|
|
8062
|
-
providerId: z
|
|
8063
|
-
domain: z
|
|
8064
|
-
protocol: z
|
|
8457
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
8458
|
+
body: z.object({
|
|
8459
|
+
providerId: z.string(),
|
|
8460
|
+
domain: z.string(),
|
|
8461
|
+
protocol: z.enum(["SAML", "OIDC"]),
|
|
8065
8462
|
samlConfig: samlConfigSchema.optional(),
|
|
8066
8463
|
oidcConfig: oidcConfigSchema.optional()
|
|
8067
8464
|
})
|
|
@@ -8140,8 +8537,8 @@ const updateSsoProvider = (options) => {
|
|
|
8140
8537
|
const requestSsoVerificationToken = (options) => {
|
|
8141
8538
|
return createAuthEndpoint("/dash/organization/:id/sso-provider/request-verification-token", {
|
|
8142
8539
|
method: "POST",
|
|
8143
|
-
use: [jwtMiddleware(options, z
|
|
8144
|
-
body: z
|
|
8540
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
8541
|
+
body: z.object({ providerId: z.string() })
|
|
8145
8542
|
}, async (ctx) => {
|
|
8146
8543
|
requireOrganizationPlugin(ctx);
|
|
8147
8544
|
requireOrganizationAccess(ctx);
|
|
@@ -8188,8 +8585,8 @@ const requestSsoVerificationToken = (options) => {
|
|
|
8188
8585
|
const verifySsoProviderDomain = (options) => {
|
|
8189
8586
|
return createAuthEndpoint("/dash/organization/:id/sso-provider/verify-domain", {
|
|
8190
8587
|
method: "POST",
|
|
8191
|
-
use: [jwtMiddleware(options, z
|
|
8192
|
-
body: z
|
|
8588
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
8589
|
+
body: z.object({ providerId: z.string() })
|
|
8193
8590
|
}, async (ctx) => {
|
|
8194
8591
|
requireOrganizationPlugin(ctx);
|
|
8195
8592
|
requireOrganizationAccess(ctx);
|
|
@@ -8235,8 +8632,8 @@ const verifySsoProviderDomain = (options) => {
|
|
|
8235
8632
|
const deleteSsoProvider = (options) => {
|
|
8236
8633
|
return createAuthEndpoint("/dash/organization/:id/sso-provider/delete", {
|
|
8237
8634
|
method: "POST",
|
|
8238
|
-
use: [jwtMiddleware(options, z
|
|
8239
|
-
body: z
|
|
8635
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
8636
|
+
body: z.object({ providerId: z.string() })
|
|
8240
8637
|
}, async (ctx) => {
|
|
8241
8638
|
requireOrganizationPlugin(ctx);
|
|
8242
8639
|
requireOrganizationAccess(ctx);
|
|
@@ -8278,10 +8675,10 @@ const deleteSsoProvider = (options) => {
|
|
|
8278
8675
|
const markSsoProviderDomainVerified = (options) => {
|
|
8279
8676
|
return createAuthEndpoint("/dash/organization/:id/sso-provider/mark-domain-verified", {
|
|
8280
8677
|
method: "POST",
|
|
8281
|
-
use: [jwtMiddleware(options, z
|
|
8282
|
-
body: z
|
|
8283
|
-
providerId: z
|
|
8284
|
-
verified: z
|
|
8678
|
+
use: [jwtMiddleware(options, z.object({ organizationId: z.string() }))],
|
|
8679
|
+
body: z.object({
|
|
8680
|
+
providerId: z.string(),
|
|
8681
|
+
verified: z.literal(false).describe("Clears domain verification. Domains can only be marked verified via DNS (verify-domain).")
|
|
8285
8682
|
})
|
|
8286
8683
|
}, async (ctx) => {
|
|
8287
8684
|
requireOrganizationPlugin(ctx);
|
|
@@ -8377,7 +8774,7 @@ function buildTotpUri(params) {
|
|
|
8377
8774
|
//#region src/routes/two-factor/index.ts
|
|
8378
8775
|
const enableTwoFactor = (options) => createAuthEndpoint("/dash/enable-two-factor", {
|
|
8379
8776
|
method: "POST",
|
|
8380
|
-
use: [jwtMiddleware(options, z
|
|
8777
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))]
|
|
8381
8778
|
}, async (ctx) => {
|
|
8382
8779
|
const { userId } = ctx.context.payload;
|
|
8383
8780
|
const twoFactorPlugin = ctx.context.getPlugin("two-factor");
|
|
@@ -8438,7 +8835,7 @@ const enableTwoFactor = (options) => createAuthEndpoint("/dash/enable-two-factor
|
|
|
8438
8835
|
});
|
|
8439
8836
|
const completeTwoFactorSetup = (options) => createAuthEndpoint("/dash/complete-two-factor-setup", {
|
|
8440
8837
|
method: "POST",
|
|
8441
|
-
use: [jwtMiddleware(options, z
|
|
8838
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))]
|
|
8442
8839
|
}, async (ctx) => {
|
|
8443
8840
|
const { userId } = ctx.context.payload;
|
|
8444
8841
|
const twoFactorPlugin = ctx.context.getPlugin("two-factor");
|
|
@@ -8466,7 +8863,7 @@ const completeTwoFactorSetup = (options) => createAuthEndpoint("/dash/complete-t
|
|
|
8466
8863
|
const viewTwoFactorTotpUri = (options) => createAuthEndpoint("/dash/view-two-factor-totp-uri", {
|
|
8467
8864
|
method: "POST",
|
|
8468
8865
|
metadata: { scope: "http" },
|
|
8469
|
-
use: [jwtMiddleware(options, z
|
|
8866
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))]
|
|
8470
8867
|
}, async (ctx) => {
|
|
8471
8868
|
const { userId } = ctx.context.payload;
|
|
8472
8869
|
const twoFactorPlugin = ctx.context.getPlugin("two-factor");
|
|
@@ -8504,13 +8901,13 @@ const viewTwoFactorTotpUri = (options) => createAuthEndpoint("/dash/view-two-fac
|
|
|
8504
8901
|
});
|
|
8505
8902
|
const viewBackupCodes = (options) => createAuthEndpoint("/dash/view-backup-codes", {
|
|
8506
8903
|
method: "POST",
|
|
8507
|
-
use: [jwtMiddleware(options, z
|
|
8904
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))]
|
|
8508
8905
|
}, async () => {
|
|
8509
8906
|
throw new APIError("FORBIDDEN", { message: "Backup codes cannot be viewed after initial setup. Generate new codes instead." });
|
|
8510
8907
|
});
|
|
8511
8908
|
const disableTwoFactor = (options) => createAuthEndpoint("/dash/disable-two-factor", {
|
|
8512
8909
|
method: "POST",
|
|
8513
|
-
use: [jwtMiddleware(options, z
|
|
8910
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))]
|
|
8514
8911
|
}, async (ctx) => {
|
|
8515
8912
|
const { userId } = ctx.context.payload;
|
|
8516
8913
|
const twoFactorPlugin = ctx.context.getPlugin("two-factor");
|
|
@@ -8531,7 +8928,7 @@ const disableTwoFactor = (options) => createAuthEndpoint("/dash/disable-two-fact
|
|
|
8531
8928
|
});
|
|
8532
8929
|
const generateBackupCodes = (options) => createAuthEndpoint("/dash/generate-backup-codes", {
|
|
8533
8930
|
method: "POST",
|
|
8534
|
-
use: [jwtMiddleware(options, z
|
|
8931
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))]
|
|
8535
8932
|
}, async (ctx) => {
|
|
8536
8933
|
const { userId } = ctx.context.payload;
|
|
8537
8934
|
const twoFactorPlugin = ctx.context.getPlugin("two-factor");
|
|
@@ -8596,13 +8993,13 @@ async function resolveTwoFactorStatus(context, userId, user) {
|
|
|
8596
8993
|
}
|
|
8597
8994
|
//#endregion
|
|
8598
8995
|
//#region src/routes/users/schemas.ts
|
|
8599
|
-
const DENIED_USER_WRITE_KEYS = new Set([
|
|
8996
|
+
const DENIED_USER_WRITE_KEYS = /* @__PURE__ */ new Set([
|
|
8600
8997
|
"id",
|
|
8601
8998
|
"createdAt",
|
|
8602
8999
|
"updatedAt"
|
|
8603
9000
|
]);
|
|
8604
9001
|
/** Fields that must be changed through dedicated permission-checked endpoints. */
|
|
8605
|
-
const DENIED_SENSITIVE_USER_KEYS = new Set([
|
|
9002
|
+
const DENIED_SENSITIVE_USER_KEYS = /* @__PURE__ */ new Set([
|
|
8606
9003
|
"banned",
|
|
8607
9004
|
"banReason",
|
|
8608
9005
|
"banExpires",
|
|
@@ -8611,7 +9008,7 @@ const DENIED_SENSITIVE_USER_KEYS = new Set([
|
|
|
8611
9008
|
"twoFactorEnabled",
|
|
8612
9009
|
"twoFactorSecret"
|
|
8613
9010
|
]);
|
|
8614
|
-
const CREATE_REQUEST_ONLY_KEYS = new Set([
|
|
9011
|
+
const CREATE_REQUEST_ONLY_KEYS = /* @__PURE__ */ new Set([
|
|
8615
9012
|
"password",
|
|
8616
9013
|
"generatePassword",
|
|
8617
9014
|
"sendVerificationEmail",
|
|
@@ -8667,7 +9064,7 @@ function getWritableUserFieldNames(options, mode) {
|
|
|
8667
9064
|
*/
|
|
8668
9065
|
function pickWritableUserFields(body, options, mode) {
|
|
8669
9066
|
const allowed = getWritableUserFieldNames(options, mode);
|
|
8670
|
-
const skip = new Set([
|
|
9067
|
+
const skip = /* @__PURE__ */ new Set([
|
|
8671
9068
|
...DENIED_USER_WRITE_KEYS,
|
|
8672
9069
|
...DENIED_SENSITIVE_USER_KEYS,
|
|
8673
9070
|
...mode === "create" ? CREATE_REQUEST_ONLY_KEYS : []
|
|
@@ -8679,32 +9076,32 @@ function pickWritableUserFields(body, options, mode) {
|
|
|
8679
9076
|
}
|
|
8680
9077
|
return result;
|
|
8681
9078
|
}
|
|
8682
|
-
const BaseCreateUserCoreBodySchema = z
|
|
8683
|
-
name: z
|
|
8684
|
-
email: z
|
|
8685
|
-
image: z
|
|
8686
|
-
password: z
|
|
8687
|
-
generatePassword: z
|
|
8688
|
-
emailVerified: z
|
|
8689
|
-
sendVerificationEmail: z
|
|
8690
|
-
sendOrganizationInvite: z
|
|
8691
|
-
organizationRole: z
|
|
8692
|
-
organizationId: z
|
|
9079
|
+
const BaseCreateUserCoreBodySchema = z.object({
|
|
9080
|
+
name: z.string(),
|
|
9081
|
+
email: z.email(),
|
|
9082
|
+
image: z.string().optional(),
|
|
9083
|
+
password: z.string().optional(),
|
|
9084
|
+
generatePassword: z.boolean().optional(),
|
|
9085
|
+
emailVerified: z.boolean().optional(),
|
|
9086
|
+
sendVerificationEmail: z.boolean().optional(),
|
|
9087
|
+
sendOrganizationInvite: z.boolean().optional(),
|
|
9088
|
+
organizationRole: z.string().optional(),
|
|
9089
|
+
organizationId: z.string().optional()
|
|
8693
9090
|
});
|
|
8694
|
-
const BaseUpdateUserCoreBodySchema = z
|
|
8695
|
-
name: z
|
|
8696
|
-
email: z
|
|
8697
|
-
image: z
|
|
8698
|
-
emailVerified: z
|
|
9091
|
+
const BaseUpdateUserCoreBodySchema = z.object({
|
|
9092
|
+
name: z.string().nullable().optional(),
|
|
9093
|
+
email: z.email().optional(),
|
|
9094
|
+
image: z.string().nullable().optional(),
|
|
9095
|
+
emailVerified: z.boolean().optional()
|
|
8699
9096
|
});
|
|
8700
|
-
const CreateUserBodySchema = BaseCreateUserCoreBodySchema.catchall(z
|
|
8701
|
-
const UpdateUserBodySchema = BaseUpdateUserCoreBodySchema.catchall(z
|
|
9097
|
+
const CreateUserBodySchema = BaseCreateUserCoreBodySchema.catchall(z.unknown());
|
|
9098
|
+
const UpdateUserBodySchema = BaseUpdateUserCoreBodySchema.catchall(z.unknown());
|
|
8702
9099
|
function createSchemaForDBField(field) {
|
|
8703
9100
|
switch (field.type) {
|
|
8704
|
-
case "number": return field.required ? z
|
|
8705
|
-
case "boolean": return field.required ? z
|
|
8706
|
-
case "date": return field.required ? z
|
|
8707
|
-
default: return field.required ? z
|
|
9101
|
+
case "number": return field.required ? z.coerce.number() : z.coerce.number().optional();
|
|
9102
|
+
case "boolean": return field.required ? z.coerce.boolean() : z.coerce.boolean().optional();
|
|
9103
|
+
case "date": return field.required ? z.union([z.string().min(1), z.coerce.date()]) : z.union([z.string(), z.coerce.date()]).optional();
|
|
9104
|
+
default: return field.required ? z.string().min(1) : z.string().optional();
|
|
8708
9105
|
}
|
|
8709
9106
|
}
|
|
8710
9107
|
/**
|
|
@@ -8712,38 +9109,38 @@ function createSchemaForDBField(field) {
|
|
|
8712
9109
|
*/
|
|
8713
9110
|
function validateWritableCreateUserFields(data, options) {
|
|
8714
9111
|
const shape = {
|
|
8715
|
-
name: z
|
|
8716
|
-
email: z
|
|
8717
|
-
image: z
|
|
8718
|
-
emailVerified: z
|
|
9112
|
+
name: z.string().min(1),
|
|
9113
|
+
email: z.email(),
|
|
9114
|
+
image: z.string().optional(),
|
|
9115
|
+
emailVerified: z.boolean().optional()
|
|
8719
9116
|
};
|
|
8720
9117
|
for (const [name, field] of Object.entries(getUserInputFields(options))) {
|
|
8721
9118
|
if (!isWritableUserInputField(field)) continue;
|
|
8722
9119
|
shape[name] = createSchemaForDBField(field);
|
|
8723
9120
|
}
|
|
8724
|
-
const result = z
|
|
9121
|
+
const result = z.object(shape).strict().safeParse(data);
|
|
8725
9122
|
if (!result.success) throw result.error;
|
|
8726
9123
|
}
|
|
8727
9124
|
/**
|
|
8728
9125
|
* Ensures at least one field is present on update and validates additional field types.
|
|
8729
9126
|
*/
|
|
8730
9127
|
function validateWritableUserUpdateFields(data, options) {
|
|
8731
|
-
if (Object.keys(data).length === 0) throw new z
|
|
9128
|
+
if (Object.keys(data).length === 0) throw new z.ZodError([{
|
|
8732
9129
|
code: "custom",
|
|
8733
9130
|
message: "No valid fields to update",
|
|
8734
9131
|
path: []
|
|
8735
9132
|
}]);
|
|
8736
9133
|
const shape = {
|
|
8737
|
-
name: z
|
|
8738
|
-
email: z
|
|
8739
|
-
image: z
|
|
8740
|
-
emailVerified: z
|
|
9134
|
+
name: z.string().nullable().optional(),
|
|
9135
|
+
email: z.email().optional(),
|
|
9136
|
+
image: z.string().nullable().optional(),
|
|
9137
|
+
emailVerified: z.boolean().optional()
|
|
8741
9138
|
};
|
|
8742
9139
|
for (const [name, field] of Object.entries(getUserInputFields(options))) {
|
|
8743
9140
|
if (!isWritableUserInputField(field)) continue;
|
|
8744
9141
|
shape[name] = createSchemaForDBField(field);
|
|
8745
9142
|
}
|
|
8746
|
-
const result = z
|
|
9143
|
+
const result = z.object(shape).partial().strict().safeParse(data);
|
|
8747
9144
|
if (!result.success) throw result.error;
|
|
8748
9145
|
}
|
|
8749
9146
|
//#endregion
|
|
@@ -8766,13 +9163,13 @@ function clampUserListOffset(value, fallback = 0) {
|
|
|
8766
9163
|
if (!Number.isFinite(n)) return fallback;
|
|
8767
9164
|
return Math.max(0, Math.floor(n));
|
|
8768
9165
|
}
|
|
8769
|
-
const getUsersQuerySchema = z
|
|
8770
|
-
limit: z
|
|
8771
|
-
offset: z
|
|
8772
|
-
sortBy: z
|
|
8773
|
-
sortOrder: z
|
|
8774
|
-
where: z
|
|
8775
|
-
countWhere: z
|
|
9166
|
+
const getUsersQuerySchema = z.object({
|
|
9167
|
+
limit: z.number().or(z.string().transform(Number)).optional(),
|
|
9168
|
+
offset: z.number().or(z.string().transform(Number)).optional(),
|
|
9169
|
+
sortBy: z.string().optional(),
|
|
9170
|
+
sortOrder: z.enum(["asc", "desc"]).optional(),
|
|
9171
|
+
where: z.string().transform(parseWhereClause).optional(),
|
|
9172
|
+
countWhere: z.string().transform(parseWhereClause).optional()
|
|
8776
9173
|
}).optional();
|
|
8777
9174
|
const getUsers = (options) => {
|
|
8778
9175
|
return createAuthEndpoint("/dash/list-users", {
|
|
@@ -8809,7 +9206,7 @@ const getUsers = (options) => {
|
|
|
8809
9206
|
model: "user",
|
|
8810
9207
|
where: [{
|
|
8811
9208
|
field: "lastActiveAt",
|
|
8812
|
-
value: /* @__PURE__ */ new Date(Date.now() -
|
|
9209
|
+
value: /* @__PURE__ */ new Date(Date.now() - 12e4),
|
|
8813
9210
|
operator: "gte"
|
|
8814
9211
|
}]
|
|
8815
9212
|
}).catch((e) => {
|
|
@@ -8867,7 +9264,7 @@ const exportUsers = (options) => {
|
|
|
8867
9264
|
const deleteUser = (options) => {
|
|
8868
9265
|
return createAuthEndpoint("/dash/delete-user", {
|
|
8869
9266
|
method: "POST",
|
|
8870
|
-
use: [jwtMiddleware(options, z
|
|
9267
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))]
|
|
8871
9268
|
}, async (ctx) => {
|
|
8872
9269
|
try {
|
|
8873
9270
|
await ctx.context.adapter.delete({
|
|
@@ -8886,7 +9283,7 @@ const deleteUser = (options) => {
|
|
|
8886
9283
|
const deleteManyUsers = (options) => {
|
|
8887
9284
|
return createAuthEndpoint("/dash/delete-many-users", {
|
|
8888
9285
|
method: "POST",
|
|
8889
|
-
use: [jwtMiddleware(options, z
|
|
9286
|
+
use: [jwtMiddleware(options, z.object({ userIds: z.string().array() }))]
|
|
8890
9287
|
}, async (ctx) => {
|
|
8891
9288
|
const { userIds } = ctx.context.payload;
|
|
8892
9289
|
const deletedUserIds = /* @__PURE__ */ new Set();
|
|
@@ -8922,11 +9319,11 @@ const deleteManyUsers = (options) => {
|
|
|
8922
9319
|
const impersonateUser = (options) => {
|
|
8923
9320
|
return createAuthEndpoint("/dash/impersonate-user", {
|
|
8924
9321
|
method: "GET",
|
|
8925
|
-
query: z
|
|
8926
|
-
use: [jwtMiddleware(options, z
|
|
8927
|
-
userId: z
|
|
9322
|
+
query: z.object({ impersonation_token: z.string() }),
|
|
9323
|
+
use: [jwtMiddleware(options, z.object({
|
|
9324
|
+
userId: z.string(),
|
|
8928
9325
|
redirectUrl: safeUrlSchema,
|
|
8929
|
-
impersonatedBy: z
|
|
9326
|
+
impersonatedBy: z.string().optional()
|
|
8930
9327
|
}), async (ctx) => {
|
|
8931
9328
|
return ctx.query.impersonation_token;
|
|
8932
9329
|
})]
|
|
@@ -8936,7 +9333,7 @@ const impersonateUser = (options) => {
|
|
|
8936
9333
|
const trustedRedirectUrl = parseTrustedAuthRedirectUrl(ctx, redirectUrl);
|
|
8937
9334
|
if (!trustedRedirectUrl) throw ctx.error("BAD_REQUEST", { message: "Invalid redirect URL" });
|
|
8938
9335
|
const session = await ctx.context.internalAdapter.createSession(userId, true, {
|
|
8939
|
-
expiresAt: new Date(Date.now() +
|
|
9336
|
+
expiresAt: new Date(Date.now() + 6e5),
|
|
8940
9337
|
impersonatedBy: impersonatedBy || void 0
|
|
8941
9338
|
});
|
|
8942
9339
|
const user = await ctx.context.internalAdapter.findUserById(userId);
|
|
@@ -8951,9 +9348,9 @@ const impersonateUser = (options) => {
|
|
|
8951
9348
|
const createUser = (options) => {
|
|
8952
9349
|
return createAuthEndpoint("/dash/create-user", {
|
|
8953
9350
|
method: "POST",
|
|
8954
|
-
use: [jwtMiddleware(options, z
|
|
8955
|
-
organizationId: z
|
|
8956
|
-
organizationRole: z
|
|
9351
|
+
use: [jwtMiddleware(options, z.object({
|
|
9352
|
+
organizationId: z.string().optional(),
|
|
9353
|
+
organizationRole: z.string().optional()
|
|
8957
9354
|
}))],
|
|
8958
9355
|
body: CreateUserBodySchema
|
|
8959
9356
|
}, async (ctx) => {
|
|
@@ -8962,7 +9359,7 @@ const createUser = (options) => {
|
|
|
8962
9359
|
try {
|
|
8963
9360
|
validateWritableCreateUserFields(userData, ctx.context.options);
|
|
8964
9361
|
} catch (error) {
|
|
8965
|
-
if (error instanceof z
|
|
9362
|
+
if (error instanceof z.ZodError) throw new APIError("BAD_REQUEST", { message: error.issues[0]?.message ?? "Invalid user data" });
|
|
8966
9363
|
throw error;
|
|
8967
9364
|
}
|
|
8968
9365
|
const email = normalizeEmail(userData.email, ctx.context);
|
|
@@ -9051,8 +9448,8 @@ const createUser = (options) => {
|
|
|
9051
9448
|
const setPassword = (options) => {
|
|
9052
9449
|
return createAuthEndpoint("/dash/set-password", {
|
|
9053
9450
|
method: "POST",
|
|
9054
|
-
use: [jwtMiddleware(options, z
|
|
9055
|
-
body: z
|
|
9451
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))],
|
|
9452
|
+
body: z.object({ password: z.string().min(8) })
|
|
9056
9453
|
}, async (ctx) => {
|
|
9057
9454
|
const { userId } = ctx.context.payload;
|
|
9058
9455
|
const { password } = ctx.body;
|
|
@@ -9073,10 +9470,10 @@ const setPassword = (options) => {
|
|
|
9073
9470
|
const unlinkAccount = (options) => {
|
|
9074
9471
|
return createAuthEndpoint("/dash/unlink-account", {
|
|
9075
9472
|
method: "POST",
|
|
9076
|
-
use: [jwtMiddleware(options, z
|
|
9077
|
-
body: z
|
|
9078
|
-
providerId: z
|
|
9079
|
-
accountId: z
|
|
9473
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))],
|
|
9474
|
+
body: z.object({
|
|
9475
|
+
providerId: z.string(),
|
|
9476
|
+
accountId: z.string()
|
|
9080
9477
|
})
|
|
9081
9478
|
}, async (ctx) => {
|
|
9082
9479
|
const { userId } = ctx.context.payload;
|
|
@@ -9094,16 +9491,16 @@ const unlinkAccount = (options) => {
|
|
|
9094
9491
|
return { success: true };
|
|
9095
9492
|
});
|
|
9096
9493
|
};
|
|
9097
|
-
const getUserDetailsJwtSchema = z
|
|
9098
|
-
userId: z
|
|
9099
|
-
sessionOnly: z
|
|
9100
|
-
accountOnly: z
|
|
9494
|
+
const getUserDetailsJwtSchema = z.object({
|
|
9495
|
+
userId: z.string(),
|
|
9496
|
+
sessionOnly: z.boolean().optional(),
|
|
9497
|
+
accountOnly: z.boolean().optional()
|
|
9101
9498
|
});
|
|
9102
9499
|
const getUserDetails = (options) => {
|
|
9103
9500
|
return createAuthEndpoint("/dash/user", {
|
|
9104
9501
|
method: "GET",
|
|
9105
9502
|
use: [jwtMiddleware(options, getUserDetailsJwtSchema)],
|
|
9106
|
-
query: z
|
|
9503
|
+
query: z.object({ minimal: z.boolean().or(z.string().transform((val) => val === "true")).optional() }).optional()
|
|
9107
9504
|
}, async (ctx) => {
|
|
9108
9505
|
const { userId, sessionOnly, accountOnly } = ctx.context.payload;
|
|
9109
9506
|
const minimal = !!ctx.query?.minimal;
|
|
@@ -9200,7 +9597,7 @@ const getUserDetails = (options) => {
|
|
|
9200
9597
|
const getUserOrganizations = (options) => {
|
|
9201
9598
|
return createAuthEndpoint("/dash/user-organizations", {
|
|
9202
9599
|
method: "GET",
|
|
9203
|
-
use: [jwtMiddleware(options, z
|
|
9600
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))]
|
|
9204
9601
|
}, async (ctx) => {
|
|
9205
9602
|
const { userId } = ctx.context.payload;
|
|
9206
9603
|
if (!isOrganizationEnabled(ctx)) {
|
|
@@ -9245,7 +9642,7 @@ const getUserOrganizations = (options) => {
|
|
|
9245
9642
|
};
|
|
9246
9643
|
const updateUser = (options) => createAuthEndpoint("/dash/update-user", {
|
|
9247
9644
|
method: "POST",
|
|
9248
|
-
use: [jwtMiddleware(options, z
|
|
9645
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))],
|
|
9249
9646
|
body: UpdateUserBodySchema
|
|
9250
9647
|
}, async (ctx) => {
|
|
9251
9648
|
const userId = ctx.context.payload?.userId;
|
|
@@ -9254,7 +9651,7 @@ const updateUser = (options) => createAuthEndpoint("/dash/update-user", {
|
|
|
9254
9651
|
try {
|
|
9255
9652
|
validateWritableUserUpdateFields(updateData, ctx.context.options);
|
|
9256
9653
|
} catch (error) {
|
|
9257
|
-
if (error instanceof z
|
|
9654
|
+
if (error instanceof z.ZodError) throw new APIError("BAD_REQUEST", { message: error.issues[0]?.message ?? "Invalid user data" });
|
|
9258
9655
|
throw error;
|
|
9259
9656
|
}
|
|
9260
9657
|
const user = await ctx.context.internalAdapter.updateUser(userId, {
|
|
@@ -9340,12 +9737,12 @@ const getUserStats = (options) => createAuthEndpoint("/dash/user-stats", {
|
|
|
9340
9737
|
use: [jwtMiddleware(options)]
|
|
9341
9738
|
}, async (ctx) => {
|
|
9342
9739
|
const now = /* @__PURE__ */ new Date();
|
|
9343
|
-
const oneDayAgo = /* @__PURE__ */ new Date(now.getTime() -
|
|
9344
|
-
const twoDaysAgo = /* @__PURE__ */ new Date(now.getTime() -
|
|
9345
|
-
const oneWeekAgo = /* @__PURE__ */ new Date(now.getTime() -
|
|
9346
|
-
const twoWeeksAgo = /* @__PURE__ */ new Date(now.getTime() -
|
|
9347
|
-
const oneMonthAgo = /* @__PURE__ */ new Date(now.getTime() -
|
|
9348
|
-
const twoMonthsAgo = /* @__PURE__ */ new Date(now.getTime() -
|
|
9740
|
+
const oneDayAgo = /* @__PURE__ */ new Date(now.getTime() - 864e5);
|
|
9741
|
+
const twoDaysAgo = /* @__PURE__ */ new Date(now.getTime() - 1728e5);
|
|
9742
|
+
const oneWeekAgo = /* @__PURE__ */ new Date(now.getTime() - 6048e5);
|
|
9743
|
+
const twoWeeksAgo = /* @__PURE__ */ new Date(now.getTime() - 12096e5);
|
|
9744
|
+
const oneMonthAgo = /* @__PURE__ */ new Date(now.getTime() - 2592e6);
|
|
9745
|
+
const twoMonthsAgo = /* @__PURE__ */ new Date(now.getTime() - 5184e6);
|
|
9349
9746
|
const activityTrackingEnabled = !!options.activityTracking?.enabled;
|
|
9350
9747
|
const storeInSecondaryStorageOnly = isSessionInSecondaryStorageOnly(ctx.context);
|
|
9351
9748
|
const [rDailySignups, rPrevDaySignups, rWeeklySignups, rPrevWeekSignups, rMonthlySignups, rPrevMonthSignups, rTotalUsers, rActiveDaily, rActivePrevDay, rActiveWeekly, rActivePrevWeek, rActiveMonthly, rActivePrevMonth] = await withConcurrency([
|
|
@@ -9480,7 +9877,7 @@ const getUserStats = (options) => createAuthEndpoint("/dash/user-stats", {
|
|
|
9480
9877
|
const getUserGraphData = (options) => createAuthEndpoint("/dash/user-graph-data", {
|
|
9481
9878
|
method: "GET",
|
|
9482
9879
|
use: [jwtMiddleware(options)],
|
|
9483
|
-
query: z
|
|
9880
|
+
query: z.object({ period: z.enum([
|
|
9484
9881
|
"daily",
|
|
9485
9882
|
"weekly",
|
|
9486
9883
|
"monthly"
|
|
@@ -9491,7 +9888,7 @@ const getUserGraphData = (options) => createAuthEndpoint("/dash/user-graph-data"
|
|
|
9491
9888
|
const activityTrackingEnabled = !!options.activityTracking?.enabled;
|
|
9492
9889
|
const storeInSecondaryStorageOnly = isSessionInSecondaryStorageOnly(ctx.context);
|
|
9493
9890
|
const intervals = period === "daily" ? 7 : period === "weekly" ? 8 : 6;
|
|
9494
|
-
const msPerInterval = period === "daily" ?
|
|
9891
|
+
const msPerInterval = period === "daily" ? 864e5 : period === "weekly" ? 6048e5 : 2592e6;
|
|
9495
9892
|
const intervalData = [];
|
|
9496
9893
|
for (let i = intervals - 1; i >= 0; i--) {
|
|
9497
9894
|
const endDate = new Date(now.getTime() - i * msPerInterval);
|
|
@@ -9552,7 +9949,7 @@ const getUserGraphData = (options) => createAuthEndpoint("/dash/user-graph-data"
|
|
|
9552
9949
|
const getUserRetentionData = (options) => createAuthEndpoint("/dash/user-retention-data", {
|
|
9553
9950
|
method: "GET",
|
|
9554
9951
|
use: [jwtMiddleware(options)],
|
|
9555
|
-
query: z
|
|
9952
|
+
query: z.object({ period: z.enum([
|
|
9556
9953
|
"daily",
|
|
9557
9954
|
"weekly",
|
|
9558
9955
|
"monthly"
|
|
@@ -9724,11 +10121,11 @@ const getUserRetentionData = (options) => createAuthEndpoint("/dash/user-retenti
|
|
|
9724
10121
|
});
|
|
9725
10122
|
const banUser = (options) => createAuthEndpoint("/dash/ban-user", {
|
|
9726
10123
|
method: "POST",
|
|
9727
|
-
use: [jwtMiddleware(options, z
|
|
9728
|
-
body: z
|
|
9729
|
-
banReason: z
|
|
9730
|
-
banExpires: z
|
|
9731
|
-
deleteAllSessions: z
|
|
10124
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))],
|
|
10125
|
+
body: z.object({
|
|
10126
|
+
banReason: z.string().optional(),
|
|
10127
|
+
banExpires: z.number().optional(),
|
|
10128
|
+
deleteAllSessions: z.boolean().optional().default(true)
|
|
9732
10129
|
})
|
|
9733
10130
|
}, async (ctx) => {
|
|
9734
10131
|
const { userId } = ctx.context.payload;
|
|
@@ -9746,11 +10143,11 @@ const banUser = (options) => createAuthEndpoint("/dash/ban-user", {
|
|
|
9746
10143
|
const banManyUsers = (options) => {
|
|
9747
10144
|
return createAuthEndpoint("/dash/ban-many-users", {
|
|
9748
10145
|
method: "POST",
|
|
9749
|
-
use: [jwtMiddleware(options, z
|
|
9750
|
-
body: z
|
|
9751
|
-
banReason: z
|
|
9752
|
-
banExpires: z
|
|
9753
|
-
deleteAllSessions: z
|
|
10146
|
+
use: [jwtMiddleware(options, z.object({ userIds: z.string().array() }))],
|
|
10147
|
+
body: z.object({
|
|
10148
|
+
banReason: z.string().optional(),
|
|
10149
|
+
banExpires: z.number().optional(),
|
|
10150
|
+
deleteAllSessions: z.boolean().optional().default(true)
|
|
9754
10151
|
})
|
|
9755
10152
|
}, async (ctx) => {
|
|
9756
10153
|
const { userIds } = ctx.context.payload;
|
|
@@ -9800,7 +10197,7 @@ const banManyUsers = (options) => {
|
|
|
9800
10197
|
};
|
|
9801
10198
|
const unbanUser = (options) => createAuthEndpoint("/dash/unban-user", {
|
|
9802
10199
|
method: "POST",
|
|
9803
|
-
use: [jwtMiddleware(options, z
|
|
10200
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))]
|
|
9804
10201
|
}, async (ctx) => {
|
|
9805
10202
|
const { userId } = ctx.context.payload;
|
|
9806
10203
|
if (!await ctx.context.internalAdapter.findUserById(userId)) throw new APIError("NOT_FOUND", { message: "User not found" });
|
|
@@ -9814,8 +10211,8 @@ const unbanUser = (options) => createAuthEndpoint("/dash/unban-user", {
|
|
|
9814
10211
|
});
|
|
9815
10212
|
const sendVerificationEmail = (options) => createAuthEndpoint("/dash/send-verification-email", {
|
|
9816
10213
|
method: "POST",
|
|
9817
|
-
use: [jwtMiddleware(options, z
|
|
9818
|
-
body: z
|
|
10214
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))],
|
|
10215
|
+
body: z.object({ callbackUrl: safeUrlSchema })
|
|
9819
10216
|
}, async (ctx) => {
|
|
9820
10217
|
const { userId } = ctx.context.payload;
|
|
9821
10218
|
const callbackUrl = requireTrustedAuthCallbackUrl(ctx, ctx.body.callbackUrl);
|
|
@@ -9824,20 +10221,21 @@ const sendVerificationEmail = (options) => createAuthEndpoint("/dash/send-verifi
|
|
|
9824
10221
|
if (user.emailVerified) throw ctx.error("BAD_REQUEST", { message: "Email is already verified" });
|
|
9825
10222
|
if (!user.email) throw ctx.error("BAD_REQUEST", { message: "User has no associated email address" });
|
|
9826
10223
|
if (!ctx.context.options.emailVerification?.sendVerificationEmail) throw ctx.error("BAD_REQUEST", { message: "Email verification is not enabled" });
|
|
9827
|
-
|
|
10224
|
+
const modifiedCtx = {
|
|
9828
10225
|
...ctx,
|
|
9829
10226
|
body: {
|
|
9830
10227
|
...ctx.body,
|
|
9831
10228
|
callbackURL: callbackUrl
|
|
9832
10229
|
}
|
|
9833
|
-
}
|
|
10230
|
+
};
|
|
10231
|
+
await sendVerificationEmailFn(modifiedCtx, user);
|
|
9834
10232
|
return { success: true };
|
|
9835
10233
|
});
|
|
9836
10234
|
const sendManyVerificationEmails = (options) => {
|
|
9837
10235
|
return createAuthEndpoint("/dash/send-many-verification-emails", {
|
|
9838
10236
|
method: "POST",
|
|
9839
|
-
use: [jwtMiddleware(options, z
|
|
9840
|
-
body: z
|
|
10237
|
+
use: [jwtMiddleware(options, z.object({ userIds: z.string().array() }))],
|
|
10238
|
+
body: z.object({ callbackUrl: safeUrlSchema })
|
|
9841
10239
|
}, async (ctx) => {
|
|
9842
10240
|
if (!ctx.context.options.emailVerification?.sendVerificationEmail) throw ctx.error("BAD_REQUEST", { message: "Email verification is not enabled" });
|
|
9843
10241
|
const { userIds } = ctx.context.payload;
|
|
@@ -9881,9 +10279,10 @@ const sendManyVerificationEmails = (options) => {
|
|
|
9881
10279
|
success: true,
|
|
9882
10280
|
id: user.id
|
|
9883
10281
|
};
|
|
9884
|
-
}))) if (result.status === "fulfilled")
|
|
9885
|
-
|
|
9886
|
-
|
|
10282
|
+
}))) if (result.status === "fulfilled") {
|
|
10283
|
+
if (result.value.success) sentEmailUserIds.add(result.value.id);
|
|
10284
|
+
else skippedEmailUserIds.add(result.value.id);
|
|
10285
|
+
} else for (const { id } of users) skippedEmailUserIds.add(id);
|
|
9887
10286
|
}, { concurrency: 2 });
|
|
9888
10287
|
const end = performance.now();
|
|
9889
10288
|
console.log(`Time taken to send verification emails to ${sentEmailUserIds.size} users: ${Math.round((end - start) / 1e3)}s`, skippedEmailUserIds.size > 0 ? `Skipped: ${skippedEmailUserIds.size}` : "");
|
|
@@ -9896,8 +10295,8 @@ const sendManyVerificationEmails = (options) => {
|
|
|
9896
10295
|
};
|
|
9897
10296
|
const sendResetPasswordEmail = (options) => createAuthEndpoint("/dash/send-reset-password-email", {
|
|
9898
10297
|
method: "POST",
|
|
9899
|
-
use: [jwtMiddleware(options, z
|
|
9900
|
-
body: z
|
|
10298
|
+
use: [jwtMiddleware(options, z.object({ userId: z.string() }))],
|
|
10299
|
+
body: z.object({ callbackUrl: safeUrlSchema })
|
|
9901
10300
|
}, async (ctx) => {
|
|
9902
10301
|
const { userId } = ctx.context.payload;
|
|
9903
10302
|
const callbackUrl = requireTrustedAuthCallbackUrl(ctx, ctx.body.callbackUrl);
|
|
@@ -9910,19 +10309,17 @@ const sendResetPasswordEmail = (options) => createAuthEndpoint("/dash/send-reset
|
|
|
9910
10309
|
});
|
|
9911
10310
|
//#endregion
|
|
9912
10311
|
//#region src/pow.ts
|
|
10312
|
+
/**
|
|
10313
|
+
* Proof of Work Challenge System - Client Side
|
|
10314
|
+
*
|
|
10315
|
+
* Client-side PoW solver and encoding utilities.
|
|
10316
|
+
* Server-side challenge generation and verification moved to Infra API.
|
|
10317
|
+
*/
|
|
9913
10318
|
/** Default difficulty in bits (18 = ~500ms solve time) */
|
|
9914
10319
|
const DEFAULT_DIFFICULTY = 18;
|
|
9915
10320
|
/** Challenge TTL in seconds */
|
|
9916
10321
|
const CHALLENGE_TTL = 60;
|
|
9917
10322
|
/**
|
|
9918
|
-
* SHA-256 hash function that works in both Node.js and browser
|
|
9919
|
-
*/
|
|
9920
|
-
async function sha256(message) {
|
|
9921
|
-
const msgBuffer = new TextEncoder().encode(message);
|
|
9922
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
|
9923
|
-
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
9924
|
-
}
|
|
9925
|
-
/**
|
|
9926
10323
|
* Check if a hash has the required number of leading zero bits
|
|
9927
10324
|
*/
|
|
9928
10325
|
function hasLeadingZeroBits(hash, bits) {
|
|
@@ -9942,7 +10339,8 @@ async function solvePoWChallenge(challenge) {
|
|
|
9942
10339
|
const { nonce, difficulty } = challenge;
|
|
9943
10340
|
let counter = 0;
|
|
9944
10341
|
while (true) {
|
|
9945
|
-
|
|
10342
|
+
const input = `${nonce}:${counter}`;
|
|
10343
|
+
if (hasLeadingZeroBits(await hash$1(input), difficulty)) return {
|
|
9946
10344
|
nonce,
|
|
9947
10345
|
counter
|
|
9948
10346
|
};
|
|
@@ -9972,7 +10370,8 @@ function encodePoWSolution(solution) {
|
|
|
9972
10370
|
* Verify a PoW solution locally (for testing purposes)
|
|
9973
10371
|
*/
|
|
9974
10372
|
async function verifyPoWSolution(nonce, counter, difficulty) {
|
|
9975
|
-
|
|
10373
|
+
const input = `${nonce}:${counter}`;
|
|
10374
|
+
return hasLeadingZeroBits(await hash$1(input), difficulty);
|
|
9976
10375
|
}
|
|
9977
10376
|
//#endregion
|
|
9978
10377
|
//#region src/sms.ts
|
|
@@ -10167,85 +10566,117 @@ const dash = (options) => {
|
|
|
10167
10566
|
const afterCreateOrganization = organizationHooks.afterCreateOrganization;
|
|
10168
10567
|
organizationHooks.afterCreateOrganization = async (...args) => {
|
|
10169
10568
|
const [{ organization, user }] = args;
|
|
10170
|
-
|
|
10569
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10570
|
+
const location = await getRequestLocation();
|
|
10571
|
+
trackOrganizationCreated(organization, trigger, location);
|
|
10171
10572
|
if (afterCreateOrganization) return afterCreateOrganization(...args);
|
|
10172
10573
|
};
|
|
10173
10574
|
const afterUpdateOrganization = organizationHooks.afterUpdateOrganization;
|
|
10174
10575
|
organizationHooks.afterUpdateOrganization = async (...args) => {
|
|
10175
10576
|
const [{ organization, user }] = args;
|
|
10176
|
-
if (organization)
|
|
10577
|
+
if (organization) {
|
|
10578
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10579
|
+
const location = await getRequestLocation();
|
|
10580
|
+
trackOrganizationUpdated(organization, trigger, location);
|
|
10581
|
+
}
|
|
10177
10582
|
if (afterUpdateOrganization) return afterUpdateOrganization(...args);
|
|
10178
10583
|
};
|
|
10179
10584
|
const afterAddMember = organizationHooks.afterAddMember;
|
|
10180
10585
|
organizationHooks.afterAddMember = async (...args) => {
|
|
10181
10586
|
const [{ organization, member, user }] = args;
|
|
10182
|
-
|
|
10587
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10588
|
+
const location = await getRequestLocation();
|
|
10589
|
+
trackOrganizationMemberAdded(organization, member, user, trigger, location);
|
|
10183
10590
|
if (afterAddMember) return afterAddMember(...args);
|
|
10184
10591
|
};
|
|
10185
10592
|
const afterRemoveMember = organizationHooks.afterRemoveMember;
|
|
10186
10593
|
organizationHooks.afterRemoveMember = async (...args) => {
|
|
10187
10594
|
const [{ organization, member, user }] = args;
|
|
10188
|
-
|
|
10595
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10596
|
+
const location = await getRequestLocation();
|
|
10597
|
+
trackOrganizationMemberRemoved(organization, member, user, trigger, location);
|
|
10189
10598
|
if (afterRemoveMember) return afterRemoveMember(...args);
|
|
10190
10599
|
};
|
|
10191
10600
|
const afterUpdateMemberRole = organizationHooks.afterUpdateMemberRole;
|
|
10192
10601
|
organizationHooks.afterUpdateMemberRole = async (...args) => {
|
|
10193
10602
|
const [{ organization, member, user, previousRole }] = args;
|
|
10194
|
-
|
|
10603
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10604
|
+
const location = await getRequestLocation();
|
|
10605
|
+
trackOrganizationMemberRoleUpdated(organization, member, user, previousRole, trigger, location);
|
|
10195
10606
|
if (afterUpdateMemberRole) return afterUpdateMemberRole(...args);
|
|
10196
10607
|
};
|
|
10197
10608
|
const afterCreateInvitation = organizationHooks.afterCreateInvitation;
|
|
10198
10609
|
organizationHooks.afterCreateInvitation = async (...args) => {
|
|
10199
10610
|
const [{ organization, invitation, inviter }] = args;
|
|
10200
|
-
|
|
10611
|
+
const trigger = getOrganizationTriggerInfo(inviter);
|
|
10612
|
+
const location = await getRequestLocation();
|
|
10613
|
+
trackOrganizationMemberInvited(organization, invitation, inviter, trigger, location);
|
|
10201
10614
|
if (afterCreateInvitation) return afterCreateInvitation(...args);
|
|
10202
10615
|
};
|
|
10203
10616
|
const afterAcceptInvitation = organizationHooks.afterAcceptInvitation;
|
|
10204
10617
|
organizationHooks.afterAcceptInvitation = async (...args) => {
|
|
10205
10618
|
const [{ organization, invitation, member, user }] = args;
|
|
10206
|
-
|
|
10619
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10620
|
+
const location = await getRequestLocation();
|
|
10621
|
+
trackOrganizationMemberInviteAccepted(organization, invitation, member, user, trigger, location);
|
|
10207
10622
|
if (afterAcceptInvitation) return afterAcceptInvitation(...args);
|
|
10208
10623
|
};
|
|
10209
10624
|
const afterRejectInvitation = organizationHooks.afterRejectInvitation;
|
|
10210
10625
|
organizationHooks.afterRejectInvitation = async (...args) => {
|
|
10211
10626
|
const [{ organization, invitation, user }] = args;
|
|
10212
|
-
|
|
10627
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10628
|
+
const location = await getRequestLocation();
|
|
10629
|
+
trackOrganizationMemberInviteRejected(organization, invitation, user, trigger, location);
|
|
10213
10630
|
if (afterRejectInvitation) return afterRejectInvitation(...args);
|
|
10214
10631
|
};
|
|
10215
10632
|
const afterCancelInvitation = organizationHooks.afterCancelInvitation;
|
|
10216
10633
|
organizationHooks.afterCancelInvitation = async (...args) => {
|
|
10217
10634
|
const [{ organization, invitation, cancelledBy }] = args;
|
|
10218
|
-
|
|
10635
|
+
const trigger = getOrganizationTriggerInfo(cancelledBy);
|
|
10636
|
+
const location = await getRequestLocation();
|
|
10637
|
+
trackOrganizationMemberInviteCanceled(organization, invitation, cancelledBy, trigger, location);
|
|
10219
10638
|
if (afterCancelInvitation) return afterCancelInvitation(...args);
|
|
10220
10639
|
};
|
|
10221
10640
|
const afterCreateTeam = organizationHooks.afterCreateTeam;
|
|
10222
10641
|
organizationHooks.afterCreateTeam = async (...args) => {
|
|
10223
10642
|
const [{ organization, team, user }] = args;
|
|
10224
|
-
|
|
10643
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10644
|
+
const location = await getRequestLocation();
|
|
10645
|
+
trackOrganizationTeamCreated(organization, team, trigger, location);
|
|
10225
10646
|
if (afterCreateTeam) return afterCreateTeam(...args);
|
|
10226
10647
|
};
|
|
10227
10648
|
const afterUpdateTeam = organizationHooks.afterUpdateTeam;
|
|
10228
10649
|
organizationHooks.afterUpdateTeam = async (...args) => {
|
|
10229
10650
|
const [{ organization, team, user }] = args;
|
|
10230
|
-
if (team)
|
|
10651
|
+
if (team) {
|
|
10652
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10653
|
+
const location = await getRequestLocation();
|
|
10654
|
+
trackOrganizationTeamUpdated(organization, team, trigger, location);
|
|
10655
|
+
}
|
|
10231
10656
|
if (afterUpdateTeam) return afterUpdateTeam(...args);
|
|
10232
10657
|
};
|
|
10233
10658
|
const afterDeleteTeam = organizationHooks.afterDeleteTeam;
|
|
10234
10659
|
organizationHooks.afterDeleteTeam = async (...args) => {
|
|
10235
10660
|
const [{ organization, team, user }] = args;
|
|
10236
|
-
|
|
10661
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10662
|
+
const location = await getRequestLocation();
|
|
10663
|
+
trackOrganizationTeamDeleted(organization, team, trigger, location);
|
|
10237
10664
|
if (afterDeleteTeam) return afterDeleteTeam(...args);
|
|
10238
10665
|
};
|
|
10239
10666
|
const afterAddTeamMember = organizationHooks.afterAddTeamMember;
|
|
10240
10667
|
organizationHooks.afterAddTeamMember = async (...args) => {
|
|
10241
10668
|
const [{ organization, team, user, teamMember }] = args;
|
|
10242
|
-
|
|
10669
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10670
|
+
const location = await getRequestLocation();
|
|
10671
|
+
trackOrganizationTeamMemberAdded(organization, team, user, teamMember, trigger, location);
|
|
10243
10672
|
if (afterAddTeamMember) return afterAddTeamMember(...args);
|
|
10244
10673
|
};
|
|
10245
10674
|
const afterRemoveTeamMember = organizationHooks.afterRemoveTeamMember;
|
|
10246
10675
|
organizationHooks.afterRemoveTeamMember = async (...args) => {
|
|
10247
10676
|
const [{ organization, team, user, teamMember }] = args;
|
|
10248
|
-
|
|
10677
|
+
const trigger = getOrganizationTriggerInfo(user);
|
|
10678
|
+
const location = await getRequestLocation();
|
|
10679
|
+
trackOrganizationTeamMemberRemoved(organization, team, user, teamMember, trigger, location);
|
|
10249
10680
|
if (afterRemoveTeamMember) return afterRemoveTeamMember(...args);
|
|
10250
10681
|
};
|
|
10251
10682
|
};
|
|
@@ -10368,7 +10799,10 @@ const dash = (options) => {
|
|
|
10368
10799
|
}
|
|
10369
10800
|
} else if (matchesAnyRoute(path, [routes.SIGN_OUT])) trackUserSignedOut(enrichedSession, trigger, ctx, location, eventUser);
|
|
10370
10801
|
else trackSessionRevoked(enrichedSession, trigger, ctx, location, eventUser);
|
|
10371
|
-
if ("impersonatedBy" in session && session.impersonatedBy)
|
|
10802
|
+
if ("impersonatedBy" in session && session.impersonatedBy) {
|
|
10803
|
+
const knownImpersonator = resolveUserFromContext(session.impersonatedBy, ctx);
|
|
10804
|
+
trackUserImpersonationStop(enrichedSession, trigger, ctx, location, eventUser, knownImpersonator);
|
|
10805
|
+
}
|
|
10372
10806
|
} }
|
|
10373
10807
|
},
|
|
10374
10808
|
account: {
|
|
@@ -10407,14 +10841,16 @@ const dash = (options) => {
|
|
|
10407
10841
|
const ctx = _ctx;
|
|
10408
10842
|
if (!ctx) return;
|
|
10409
10843
|
const path = ctx.path;
|
|
10410
|
-
const
|
|
10844
|
+
const maybeUserId = ctx.context.session?.user.id ?? "unknown";
|
|
10845
|
+
const trigger = getTriggerInfo(ctx, maybeUserId);
|
|
10411
10846
|
const location = ctx.context.location;
|
|
10412
10847
|
if (matchesAnyRoute(path, [routes.REQUEST_PASSWORD_RESET])) trackPasswordResetRequest(verification, trigger, ctx, location);
|
|
10413
10848
|
} },
|
|
10414
10849
|
delete: { async after(verification, ctx) {
|
|
10415
10850
|
if (!ctx) return;
|
|
10416
10851
|
const path = ctx.path;
|
|
10417
|
-
const
|
|
10852
|
+
const maybeUserId = ctx.context.session?.user.id ?? "unknown";
|
|
10853
|
+
const trigger = getTriggerInfo(ctx, maybeUserId);
|
|
10418
10854
|
const location = ctx.context.location;
|
|
10419
10855
|
if (matchesAnyRoute(path, [routes.RESET_PASSWORD])) trackPasswordResetRequestCompletion(verification, trigger, ctx, location);
|
|
10420
10856
|
} }
|
|
@@ -10425,18 +10861,7 @@ const dash = (options) => {
|
|
|
10425
10861
|
},
|
|
10426
10862
|
hooks: {
|
|
10427
10863
|
before: [{
|
|
10428
|
-
matcher: (ctx) =>
|
|
10429
|
-
if (ctx.request?.method !== "GET") return true;
|
|
10430
|
-
return matchesAnyRoute(ctx.path, [
|
|
10431
|
-
routes.SIGN_IN_SOCIAL_CALLBACK,
|
|
10432
|
-
routes.SIGN_IN_OAUTH_CALLBACK,
|
|
10433
|
-
routes.DASH_IMPERSONATE_USER,
|
|
10434
|
-
routes.VERIFY_EMAIL,
|
|
10435
|
-
routes.MAGIC_LINK_VERIFY,
|
|
10436
|
-
routes.DASH_ACCEPT_INVITATION,
|
|
10437
|
-
routes.DASH_COMPLETE_INVITATION_SOCIAL
|
|
10438
|
-
]);
|
|
10439
|
-
},
|
|
10864
|
+
matcher: (ctx) => shouldRunIdentification(ctx, IDENTIFICATION_GET_ROUTES),
|
|
10440
10865
|
handler: createIdentificationMiddleware($kv, {
|
|
10441
10866
|
skipIdentification: (ctx) => isDashRoute(ctx.path),
|
|
10442
10867
|
retry: opts.kvOptions.retry
|
|
@@ -10462,25 +10887,19 @@ const dash = (options) => {
|
|
|
10462
10887
|
},
|
|
10463
10888
|
handler: createAuthMiddleware(async (_ctx) => {
|
|
10464
10889
|
const ctx = _ctx;
|
|
10465
|
-
const
|
|
10890
|
+
const maybeUserId = ctx.context.session?.user.id ?? "unknown";
|
|
10891
|
+
const trigger = getTriggerInfo(ctx, maybeUserId);
|
|
10466
10892
|
if (matchesAnyRoute(ctx.path, [routes.SEND_VERIFICATION_EMAIL, routes.DASH_SEND_VERIFICATION_EMAIL]) && ctx.context.session && !(ctx.context.returned instanceof Error)) trackEmailVerificationSent(ctx.context.session.session, ctx.context.session.user, trigger, ctx.context.location);
|
|
10467
10893
|
const body = ctx.body;
|
|
10468
10894
|
if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_EMAIL, routes.SIGN_IN_EMAIL_OTP]) && ctx.context.returned instanceof Error && body?.email) trackEmailSignInAttempt(ctx, trigger, ctx.context.location);
|
|
10469
10895
|
if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL]) && ctx.context.returned instanceof Error && ctx.body.provider && ctx.body.idToken) trackSocialSignInAttempt(ctx, trigger, ctx.context.location);
|
|
10470
10896
|
if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL_CALLBACK, routes.SIGN_IN_OAUTH_CALLBACK]) && ctx.request?.method === "GET" && !ctx.context.newSession) trackSocialSignInRedirectionAttempt(ctx, trigger, ctx.context.location);
|
|
10471
|
-
const headerRequestId = ctx.request?.headers.get("X-Request-Id");
|
|
10472
|
-
if (headerRequestId) ctx.setCookie(IDENTIFICATION_COOKIE_NAME, headerRequestId, {
|
|
10473
|
-
maxAge: 600,
|
|
10474
|
-
sameSite: "lax",
|
|
10475
|
-
httpOnly: true,
|
|
10476
|
-
path: "/"
|
|
10477
|
-
});
|
|
10478
|
-
else if (ctx.context.requestId) ctx.setCookie(IDENTIFICATION_COOKIE_NAME, "", {
|
|
10479
|
-
maxAge: 0,
|
|
10480
|
-
path: "/"
|
|
10481
|
-
});
|
|
10482
10897
|
})
|
|
10483
10898
|
},
|
|
10899
|
+
{
|
|
10900
|
+
matcher: (ctx) => shouldRunIdentification(ctx, IDENTIFICATION_GET_ROUTES),
|
|
10901
|
+
handler: createIdentificationCookieAfterMiddleware()
|
|
10902
|
+
},
|
|
10484
10903
|
{
|
|
10485
10904
|
handler: createAuthMiddleware(async (ctx) => {
|
|
10486
10905
|
if (!opts.activityTracking?.enabled) return;
|