@better-auth/infra 0.1.8 → 0.1.9
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/dist/client.d.mts +5 -5
- package/dist/client.mjs +3 -1
- package/dist/constants-B-e0_Nsv.mjs +18 -0
- package/dist/email.mjs +181 -1
- package/dist/index.d.mts +271 -258
- package/dist/index.mjs +246 -133
- package/package.json +13 -10
- package/dist/email-D2dL1i3c.mjs +0 -195
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as INFRA_KV_URL, r as KV_TIMEOUT_MS, t as INFRA_API_URL } from "./constants-B-e0_Nsv.mjs";
|
|
2
|
+
import { EMAIL_TEMPLATES, createEmailSender, sendBulkEmails, sendEmail } from "./email.mjs";
|
|
2
3
|
import { APIError, generateId, getAuthTables, logger, parseState } from "better-auth";
|
|
3
4
|
import { env } from "@better-auth/core/env";
|
|
4
5
|
import { APIError as APIError$1, createAuthEndpoint, createAuthMiddleware, requestPasswordReset, sendVerificationEmailFn, sessionMiddleware } from "better-auth/api";
|
|
6
|
+
import { getCurrentAuthContext } from "@better-auth/core/context";
|
|
5
7
|
import { betterFetch, createFetch } from "@better-fetch/fetch";
|
|
6
8
|
import { isValidPhoneNumber, parsePhoneNumberFromString } from "libphonenumber-js";
|
|
7
9
|
import { createLocalJWKSet, jwtVerify } from "jose";
|
|
@@ -132,21 +134,6 @@ const ORGANIZATION_EVENT_TYPES = {
|
|
|
132
134
|
ORGANIZATION_TEAM_MEMBER_REMOVED: "organization_team_member_removed"
|
|
133
135
|
};
|
|
134
136
|
|
|
135
|
-
//#endregion
|
|
136
|
-
//#region src/events/utils.ts
|
|
137
|
-
function backgroundTask(task) {
|
|
138
|
-
let result;
|
|
139
|
-
try {
|
|
140
|
-
result = task();
|
|
141
|
-
} catch (error) {
|
|
142
|
-
logger.debug("[Dash] Background operation failed: ", error);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
Promise.resolve(result).catch((error) => {
|
|
146
|
-
logger.debug("[Dash] Background operation failed: ", error);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
137
|
//#endregion
|
|
151
138
|
//#region src/events/core/adapter.ts
|
|
152
139
|
const getUserByEmail = async (email, ctx) => {
|
|
@@ -244,7 +231,7 @@ const initAccountEvents = (tracker) => {
|
|
|
244
231
|
countryCode: location?.countryCode
|
|
245
232
|
});
|
|
246
233
|
};
|
|
247
|
-
|
|
234
|
+
ctx.context.runInBackground(track());
|
|
248
235
|
};
|
|
249
236
|
const trackAccountUnlink = (account, trigger, ctx, location) => {
|
|
250
237
|
const track = async () => {
|
|
@@ -268,7 +255,7 @@ const initAccountEvents = (tracker) => {
|
|
|
268
255
|
countryCode: location?.countryCode
|
|
269
256
|
});
|
|
270
257
|
};
|
|
271
|
-
|
|
258
|
+
ctx.context.runInBackground(track());
|
|
272
259
|
};
|
|
273
260
|
const trackAccountPasswordChange = (account, trigger, ctx, location) => {
|
|
274
261
|
const track = async () => {
|
|
@@ -292,7 +279,7 @@ const initAccountEvents = (tracker) => {
|
|
|
292
279
|
countryCode: location?.countryCode
|
|
293
280
|
});
|
|
294
281
|
};
|
|
295
|
-
|
|
282
|
+
ctx.context.runInBackground(track());
|
|
296
283
|
};
|
|
297
284
|
return {
|
|
298
285
|
trackAccountLinking,
|
|
@@ -306,7 +293,7 @@ const initAccountEvents = (tracker) => {
|
|
|
306
293
|
const stripQuery = (value) => value.split("?")[0] || value;
|
|
307
294
|
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
308
295
|
const routeToRegex = (route) => {
|
|
309
|
-
const pattern = escapeRegex(stripQuery(route)).replace(
|
|
296
|
+
const pattern = escapeRegex(stripQuery(route)).replace(/\/:([^/]+)/g, "/[^/]+");
|
|
310
297
|
return /* @__PURE__ */ new RegExp(`${pattern}(?:$|[/?])`);
|
|
311
298
|
};
|
|
312
299
|
const matchesAnyRoute = (path, routes$1) => {
|
|
@@ -357,7 +344,7 @@ const initSessionEvents = (tracker) => {
|
|
|
357
344
|
countryCode: location?.countryCode
|
|
358
345
|
});
|
|
359
346
|
};
|
|
360
|
-
|
|
347
|
+
ctx.context.runInBackground(track());
|
|
361
348
|
};
|
|
362
349
|
const trackUserSignedOut = (session, trigger, ctx, location) => {
|
|
363
350
|
const track = async () => {
|
|
@@ -383,7 +370,7 @@ const initSessionEvents = (tracker) => {
|
|
|
383
370
|
countryCode: location?.countryCode
|
|
384
371
|
});
|
|
385
372
|
};
|
|
386
|
-
|
|
373
|
+
ctx.context.runInBackground(track());
|
|
387
374
|
};
|
|
388
375
|
const trackSessionRevoked = (session, trigger, ctx, location) => {
|
|
389
376
|
const track = async () => {
|
|
@@ -409,7 +396,7 @@ const initSessionEvents = (tracker) => {
|
|
|
409
396
|
countryCode: location?.countryCode
|
|
410
397
|
});
|
|
411
398
|
};
|
|
412
|
-
|
|
399
|
+
ctx.context.runInBackground(track());
|
|
413
400
|
};
|
|
414
401
|
const trackSessionRevokedAll = (session, trigger, ctx, _location) => {
|
|
415
402
|
const track = async () => {
|
|
@@ -427,7 +414,7 @@ const initSessionEvents = (tracker) => {
|
|
|
427
414
|
}
|
|
428
415
|
});
|
|
429
416
|
};
|
|
430
|
-
|
|
417
|
+
ctx.context.runInBackground(track());
|
|
431
418
|
};
|
|
432
419
|
const trackUserImpersonated = (session, trigger, ctx, location) => {
|
|
433
420
|
const track = async () => {
|
|
@@ -456,7 +443,7 @@ const initSessionEvents = (tracker) => {
|
|
|
456
443
|
countryCode: location?.countryCode
|
|
457
444
|
});
|
|
458
445
|
};
|
|
459
|
-
|
|
446
|
+
ctx.context.runInBackground(track());
|
|
460
447
|
};
|
|
461
448
|
const trackUserImpersonationStop = (session, trigger, ctx, location) => {
|
|
462
449
|
const track = async () => {
|
|
@@ -485,7 +472,7 @@ const initSessionEvents = (tracker) => {
|
|
|
485
472
|
countryCode: location?.countryCode
|
|
486
473
|
});
|
|
487
474
|
};
|
|
488
|
-
|
|
475
|
+
ctx.context.runInBackground(track());
|
|
489
476
|
};
|
|
490
477
|
const trackSessionCreated = (session, trigger, ctx, location) => {
|
|
491
478
|
const track = async () => {
|
|
@@ -511,7 +498,7 @@ const initSessionEvents = (tracker) => {
|
|
|
511
498
|
countryCode: location?.countryCode
|
|
512
499
|
});
|
|
513
500
|
};
|
|
514
|
-
|
|
501
|
+
ctx.context.runInBackground(track());
|
|
515
502
|
};
|
|
516
503
|
const trackEmailVerificationSent = (session, user, trigger, _ctx) => {
|
|
517
504
|
trackEvent({
|
|
@@ -545,7 +532,7 @@ const initSessionEvents = (tracker) => {
|
|
|
545
532
|
}
|
|
546
533
|
});
|
|
547
534
|
};
|
|
548
|
-
|
|
535
|
+
ctx.context.runInBackground(track());
|
|
549
536
|
};
|
|
550
537
|
const trackSocialSignInAttempt = (ctx, trigger) => {
|
|
551
538
|
const track = async () => {
|
|
@@ -564,11 +551,11 @@ const initSessionEvents = (tracker) => {
|
|
|
564
551
|
}
|
|
565
552
|
});
|
|
566
553
|
};
|
|
567
|
-
|
|
554
|
+
ctx.context.runInBackground(track());
|
|
568
555
|
};
|
|
569
556
|
const trackSocialSignInRedirectionAttempt = (ctx, trigger) => {
|
|
570
557
|
const track = async () => {
|
|
571
|
-
const user = await getUserByAuthorizationCode(ctx.
|
|
558
|
+
const user = await getUserByAuthorizationCode(ctx.params?.id, ctx);
|
|
572
559
|
trackEvent({
|
|
573
560
|
eventKey: user?.id.toString() ?? UNKNOWN_USER,
|
|
574
561
|
eventType: EVENT_TYPES.USER_SIGN_IN_FAILED,
|
|
@@ -583,7 +570,7 @@ const initSessionEvents = (tracker) => {
|
|
|
583
570
|
}
|
|
584
571
|
});
|
|
585
572
|
};
|
|
586
|
-
|
|
573
|
+
ctx.context.runInBackground(track());
|
|
587
574
|
};
|
|
588
575
|
return {
|
|
589
576
|
trackUserSignedIn,
|
|
@@ -772,7 +759,7 @@ const initVerificationEvents = (tracker) => {
|
|
|
772
759
|
countryCode: location?.countryCode
|
|
773
760
|
});
|
|
774
761
|
};
|
|
775
|
-
|
|
762
|
+
ctx.context.runInBackground(track());
|
|
776
763
|
};
|
|
777
764
|
const trackPasswordResetRequestCompletion = (verification, trigger, ctx, location) => {
|
|
778
765
|
const track = async () => {
|
|
@@ -794,7 +781,7 @@ const initVerificationEvents = (tracker) => {
|
|
|
794
781
|
countryCode: location?.countryCode
|
|
795
782
|
});
|
|
796
783
|
};
|
|
797
|
-
|
|
784
|
+
ctx.context.runInBackground(track());
|
|
798
785
|
};
|
|
799
786
|
return {
|
|
800
787
|
trackPasswordResetRequest,
|
|
@@ -805,7 +792,7 @@ const initVerificationEvents = (tracker) => {
|
|
|
805
792
|
//#endregion
|
|
806
793
|
//#region src/events/triggers.ts
|
|
807
794
|
const getTriggerInfo = (ctx, userId, session) => {
|
|
808
|
-
const sessionUserId = session?.userId ?? ctx.context.session?.session.userId ??
|
|
795
|
+
const sessionUserId = session?.userId ?? ctx.context.session?.session.userId ?? userId;
|
|
809
796
|
return {
|
|
810
797
|
triggeredBy: sessionUserId,
|
|
811
798
|
triggerContext: sessionUserId === userId ? "user" : matchesAnyRoute(ctx.path, [routes.ADMIN_ROUTE]) ? "admin" : matchesAnyRoute(ctx.path, [routes.DASH_ROUTE]) ? "dashboard" : sessionUserId === UNKNOWN_USER ? "user" : "unknown"
|
|
@@ -850,7 +837,13 @@ const initTrackEvents = (options) => {
|
|
|
850
837
|
logger.debug("[Dash] Failed to track event:", e);
|
|
851
838
|
}
|
|
852
839
|
};
|
|
853
|
-
|
|
840
|
+
const onSuccess = (ctx) => {
|
|
841
|
+
ctx.context.runInBackground(track());
|
|
842
|
+
};
|
|
843
|
+
const onError = () => {
|
|
844
|
+
track();
|
|
845
|
+
};
|
|
846
|
+
getCurrentAuthContext().then(onSuccess, onError);
|
|
854
847
|
};
|
|
855
848
|
return { tracker: { trackEvent } };
|
|
856
849
|
};
|
|
@@ -863,6 +856,7 @@ const initTrackEvents = (options) => {
|
|
|
863
856
|
* Fetches identification data from the durable-kv service
|
|
864
857
|
* when a request includes an X-Request-Id header.
|
|
865
858
|
*/
|
|
859
|
+
const IDENTIFICATION_COOKIE_NAME = "__infra-rid";
|
|
866
860
|
const identificationCache = /* @__PURE__ */ new Map();
|
|
867
861
|
const CACHE_TTL_MS = 6e4;
|
|
868
862
|
const CACHE_MAX_SIZE = 1e3;
|
|
@@ -892,7 +886,8 @@ async function getIdentification(requestId, apiKey, kvUrl) {
|
|
|
892
886
|
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
893
887
|
const response = await fetch(`${baseUrl}/identify/${requestId}`, {
|
|
894
888
|
method: "GET",
|
|
895
|
-
headers: { "x-api-key": apiKey }
|
|
889
|
+
headers: { "x-api-key": apiKey },
|
|
890
|
+
signal: AbortSignal.timeout(KV_TIMEOUT_MS)
|
|
896
891
|
});
|
|
897
892
|
if (response.ok) {
|
|
898
893
|
const data = await response.json();
|
|
@@ -938,7 +933,8 @@ function extractIdentificationHeaders(request) {
|
|
|
938
933
|
*/
|
|
939
934
|
function createIdentificationMiddleware(options) {
|
|
940
935
|
return createAuthMiddleware(async (ctx) => {
|
|
941
|
-
const { visitorId, requestId } = extractIdentificationHeaders(ctx.request);
|
|
936
|
+
const { visitorId, requestId: headerRequestId } = extractIdentificationHeaders(ctx.request);
|
|
937
|
+
const requestId = headerRequestId ?? ctx.getCookie(IDENTIFICATION_COOKIE_NAME) ?? null;
|
|
942
938
|
ctx.context.visitorId = visitorId;
|
|
943
939
|
ctx.context.requestId = requestId;
|
|
944
940
|
if (requestId) ctx.context.identification = ctx.context.identification ?? await getIdentification(requestId, options.apiKey, options.kvUrl) ?? null;
|
|
@@ -1249,9 +1245,9 @@ function createSecurityClient(apiUrl, apiKey, options, onSecurityEvent) {
|
|
|
1249
1245
|
},
|
|
1250
1246
|
async checkCompromisedPassword(password) {
|
|
1251
1247
|
try {
|
|
1252
|
-
const hash = await sha1Hash(password);
|
|
1253
|
-
const prefix = hash.substring(0, 5);
|
|
1254
|
-
const suffix = hash.substring(5);
|
|
1248
|
+
const hash$1 = await sha1Hash(password);
|
|
1249
|
+
const prefix = hash$1.substring(0, 5);
|
|
1250
|
+
const suffix = hash$1.substring(5);
|
|
1255
1251
|
const data = await $api("/security/breached-passwords", {
|
|
1256
1252
|
method: "POST",
|
|
1257
1253
|
body: {
|
|
@@ -1633,7 +1629,8 @@ function createEmailValidator(options = {}) {
|
|
|
1633
1629
|
});
|
|
1634
1630
|
const $kv = createFetch({
|
|
1635
1631
|
baseURL: kvUrl,
|
|
1636
|
-
headers: { "x-api-key": apiKey }
|
|
1632
|
+
headers: { "x-api-key": apiKey },
|
|
1633
|
+
timeout: KV_TIMEOUT_MS
|
|
1637
1634
|
});
|
|
1638
1635
|
/**
|
|
1639
1636
|
* Fetch and resolve email validity policy from API with caching
|
|
@@ -2037,7 +2034,7 @@ const sentinel = (options) => {
|
|
|
2037
2034
|
const { trackEvent } = tracker;
|
|
2038
2035
|
if (!opts.apiKey) logger.warn("[Sentinel] Missing BETTER_AUTH_API_KEY. Security checks may fall back to allow mode when the Infra API rejects requests.");
|
|
2039
2036
|
const securityService = createSecurityClient(opts.apiUrl, opts.apiKey, opts.security || {}, (event) => {
|
|
2040
|
-
|
|
2037
|
+
trackEvent({
|
|
2041
2038
|
eventKey: event.visitorId || event.userId || "unknown",
|
|
2042
2039
|
eventType: `security_${event.type}`,
|
|
2043
2040
|
eventDisplayName: `Security: ${event.type.replace(/_/g, " ")}`,
|
|
@@ -2068,7 +2065,7 @@ const sentinel = (options) => {
|
|
|
2068
2065
|
const displayName = isNoMxRecord ? `Security: invalid email domain ${actionVerb}` : `Security: disposable email ${actionVerb}`;
|
|
2069
2066
|
const description = isNoMxRecord ? `${actionVerb.charAt(0).toUpperCase() + actionVerb.slice(1)} signup attempt with invalid email domain (no MX records): ${data.email}` : `${actionVerb.charAt(0).toUpperCase() + actionVerb.slice(1)} signup attempt with disposable email: ${data.email} (${data.reason}, ${data.confidence} confidence)`;
|
|
2070
2067
|
logger.info(`[Sentinel] Tracking ${reason} event for email: ${data.email} (action: ${actionLabel})`);
|
|
2071
|
-
|
|
2068
|
+
trackEvent({
|
|
2072
2069
|
eventKey: data.email,
|
|
2073
2070
|
eventType,
|
|
2074
2071
|
eventDisplayName: displayName,
|
|
@@ -2086,28 +2083,28 @@ const sentinel = (options) => {
|
|
|
2086
2083
|
});
|
|
2087
2084
|
return {
|
|
2088
2085
|
id: "sentinel",
|
|
2089
|
-
init(
|
|
2086
|
+
init() {
|
|
2090
2087
|
return { options: { databaseHooks: {
|
|
2091
2088
|
user: { create: {
|
|
2092
|
-
async before(_user,
|
|
2093
|
-
if (!
|
|
2094
|
-
const visitorId =
|
|
2089
|
+
async before(_user, ctx) {
|
|
2090
|
+
if (!ctx) return;
|
|
2091
|
+
const visitorId = ctx.context.visitorId;
|
|
2095
2092
|
if (visitorId && opts.security?.freeTrialAbuse?.enabled) {
|
|
2096
2093
|
const abuseCheck = await securityService.checkFreeTrialAbuse(visitorId);
|
|
2097
2094
|
if (abuseCheck.isAbuse && abuseCheck.action === "block") throw new APIError("FORBIDDEN", { message: "Account creation is not allowed from this device." });
|
|
2098
2095
|
}
|
|
2099
2096
|
},
|
|
2100
|
-
async after(user,
|
|
2101
|
-
if (!
|
|
2102
|
-
const visitorId =
|
|
2103
|
-
if (visitorId && opts.security?.freeTrialAbuse?.enabled)
|
|
2097
|
+
async after(user, ctx) {
|
|
2098
|
+
if (!ctx) return;
|
|
2099
|
+
const visitorId = ctx.context.visitorId;
|
|
2100
|
+
if (visitorId && opts.security?.freeTrialAbuse?.enabled) await ctx.context.runInBackgroundOrAwait(securityService.trackFreeTrialSignup(visitorId, user.id));
|
|
2104
2101
|
}
|
|
2105
2102
|
} },
|
|
2106
2103
|
session: { create: {
|
|
2107
|
-
async before(session,
|
|
2108
|
-
if (!
|
|
2109
|
-
const visitorId =
|
|
2110
|
-
const identification =
|
|
2104
|
+
async before(session, ctx) {
|
|
2105
|
+
if (!ctx) return;
|
|
2106
|
+
const visitorId = ctx.context.visitorId;
|
|
2107
|
+
const identification = ctx.context.identification;
|
|
2111
2108
|
if (session.userId && identification?.location && visitorId) {
|
|
2112
2109
|
const travelCheck = await securityService.checkImpossibleTravel(session.userId, identification.location, visitorId);
|
|
2113
2110
|
if (travelCheck?.isImpossible) {
|
|
@@ -2116,13 +2113,13 @@ const sentinel = (options) => {
|
|
|
2116
2113
|
}
|
|
2117
2114
|
}
|
|
2118
2115
|
},
|
|
2119
|
-
async after(session,
|
|
2120
|
-
if (!
|
|
2121
|
-
const visitorId =
|
|
2122
|
-
const identification =
|
|
2116
|
+
async after(session, ctx) {
|
|
2117
|
+
if (!ctx || !session.userId) return;
|
|
2118
|
+
const visitorId = ctx.context.visitorId;
|
|
2119
|
+
const identification = ctx.context.identification;
|
|
2123
2120
|
let user = null;
|
|
2124
2121
|
try {
|
|
2125
|
-
user = await
|
|
2122
|
+
user = await ctx.context.adapter.findOne({
|
|
2126
2123
|
model: "user",
|
|
2127
2124
|
select: [
|
|
2128
2125
|
"email",
|
|
@@ -2138,7 +2135,7 @@ const sentinel = (options) => {
|
|
|
2138
2135
|
logger.warn("[Sentinel] Failed to fetch user for security checks:", error);
|
|
2139
2136
|
}
|
|
2140
2137
|
if (visitorId) {
|
|
2141
|
-
if (await securityService.checkUnknownDevice(session.userId, visitorId) && user?.email)
|
|
2138
|
+
if (await securityService.checkUnknownDevice(session.userId, visitorId) && user?.email) await ctx.context.runInBackgroundOrAwait(securityService.notifyUnknownDevice(session.userId, user.email, identification));
|
|
2142
2139
|
}
|
|
2143
2140
|
if (opts.security?.staleUsers?.enabled && user) {
|
|
2144
2141
|
const staleCheck = await securityService.checkStaleUser(session.userId, user.lastActiveAt || null);
|
|
@@ -2177,7 +2174,7 @@ const sentinel = (options) => {
|
|
|
2177
2174
|
});
|
|
2178
2175
|
}
|
|
2179
2176
|
}
|
|
2180
|
-
if (identification?.location)
|
|
2177
|
+
if (identification?.location) await ctx.context.runInBackgroundOrAwait(securityService.storeLastLocation(session.userId, identification.location));
|
|
2181
2178
|
}
|
|
2182
2179
|
} }
|
|
2183
2180
|
} } };
|
|
@@ -2190,11 +2187,11 @@ const sentinel = (options) => {
|
|
|
2190
2187
|
},
|
|
2191
2188
|
{
|
|
2192
2189
|
matcher: (ctx) => ctx.request?.method !== "GET",
|
|
2193
|
-
handler: createAuthMiddleware(async (
|
|
2194
|
-
const visitorId =
|
|
2195
|
-
const powSolution =
|
|
2190
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
2191
|
+
const visitorId = ctx.context.visitorId;
|
|
2192
|
+
const powSolution = ctx.headers?.get?.("X-PoW-Solution");
|
|
2196
2193
|
if (visitorId && powSolution) {
|
|
2197
|
-
if ((await securityService.verifyPoWSolution(visitorId, powSolution)).valid)
|
|
2194
|
+
if ((await securityService.verifyPoWSolution(visitorId, powSolution)).valid) ctx.context.powVerified = true;
|
|
2198
2195
|
}
|
|
2199
2196
|
})
|
|
2200
2197
|
},
|
|
@@ -2202,10 +2199,10 @@ const sentinel = (options) => {
|
|
|
2202
2199
|
...phoneValidationHooks.before,
|
|
2203
2200
|
{
|
|
2204
2201
|
matcher: (ctx) => ctx.request?.method !== "GET",
|
|
2205
|
-
handler: createAuthMiddleware(async (
|
|
2206
|
-
const visitorId =
|
|
2207
|
-
const identification =
|
|
2208
|
-
const isSignIn = matchesAnyRoute(
|
|
2202
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
2203
|
+
const visitorId = ctx.context.visitorId;
|
|
2204
|
+
const identification = ctx.context.identification;
|
|
2205
|
+
const isSignIn = matchesAnyRoute(ctx.path, [
|
|
2209
2206
|
routes.SIGN_IN_EMAIL,
|
|
2210
2207
|
routes.SIGN_IN_USERNAME,
|
|
2211
2208
|
routes.SIGN_IN_EMAIL_OTP,
|
|
@@ -2215,17 +2212,17 @@ const sentinel = (options) => {
|
|
|
2215
2212
|
routes.SIGN_IN_SSO,
|
|
2216
2213
|
routes.SIGN_IN_ANONYMOUS
|
|
2217
2214
|
]);
|
|
2218
|
-
const isSignUp = matchesAnyRoute(
|
|
2219
|
-
const isPasswordReset = matchesAnyRoute(
|
|
2220
|
-
const isTwoFactor = matchesAnyRoute(
|
|
2215
|
+
const isSignUp = matchesAnyRoute(ctx.path, [routes.SIGN_UP_EMAIL]);
|
|
2216
|
+
const isPasswordReset = matchesAnyRoute(ctx.path, [routes.FORGET_PASSWORD, routes.REQUEST_PASSWORD_RESET]);
|
|
2217
|
+
const isTwoFactor = matchesAnyRoute(ctx.path, [
|
|
2221
2218
|
routes.TWO_FACTOR_VERIFY_TOTP,
|
|
2222
2219
|
routes.TWO_FACTOR_VERIFY_BACKUP,
|
|
2223
2220
|
routes.TWO_FACTOR_VERIFY_OTP
|
|
2224
2221
|
]);
|
|
2225
|
-
const isOtpSend = matchesAnyRoute(
|
|
2226
|
-
const isMagicLinkVerify = matchesAnyRoute(
|
|
2227
|
-
const isOrgCreate = matchesAnyRoute(
|
|
2228
|
-
const isSensitiveAction = matchesAnyRoute(
|
|
2222
|
+
const isOtpSend = matchesAnyRoute(ctx.path, [routes.EMAIL_OTP_SEND, routes.PHONE_SEND_OTP]);
|
|
2223
|
+
const isMagicLinkVerify = matchesAnyRoute(ctx.path, [routes.MAGIC_LINK_VERIFY]);
|
|
2224
|
+
const isOrgCreate = matchesAnyRoute(ctx.path, [routes.ORG_CREATE]);
|
|
2225
|
+
const isSensitiveAction = matchesAnyRoute(ctx.path, [
|
|
2229
2226
|
routes.CHANGE_EMAIL,
|
|
2230
2227
|
routes.CHANGE_PASSWORD,
|
|
2231
2228
|
routes.SET_PASSWORD,
|
|
@@ -2233,9 +2230,9 @@ const sentinel = (options) => {
|
|
|
2233
2230
|
routes.PASSKEY_ADD
|
|
2234
2231
|
]);
|
|
2235
2232
|
if (!(isSignIn || isSignUp || isPasswordReset || isTwoFactor || isOtpSend || isMagicLinkVerify || isOrgCreate || isSensitiveAction)) return;
|
|
2236
|
-
const requestBody =
|
|
2233
|
+
const requestBody = ctx.body;
|
|
2237
2234
|
const identifier = requestBody?.email || requestBody?.phone || requestBody?.username || void 0;
|
|
2238
|
-
const powVerified =
|
|
2235
|
+
const powVerified = ctx.context.powVerified === true;
|
|
2239
2236
|
if (visitorId && powVerified) trackEvent({
|
|
2240
2237
|
eventKey: visitorId,
|
|
2241
2238
|
eventType: "security_allowed",
|
|
@@ -2244,8 +2241,8 @@ const sentinel = (options) => {
|
|
|
2244
2241
|
action: "allowed",
|
|
2245
2242
|
reason: "pow_verified",
|
|
2246
2243
|
visitorId,
|
|
2247
|
-
path:
|
|
2248
|
-
userAgent:
|
|
2244
|
+
path: ctx.path,
|
|
2245
|
+
userAgent: ctx.headers?.get?.("user-agent") || "",
|
|
2249
2246
|
identifier,
|
|
2250
2247
|
detectionLabel: "Challenge Completed",
|
|
2251
2248
|
description: identifier ? `Successfully completed security challenge for "${identifier}"` : "Successfully completed security challenge"
|
|
@@ -2265,8 +2262,8 @@ const sentinel = (options) => {
|
|
|
2265
2262
|
action: "blocked",
|
|
2266
2263
|
reason: "credential_stuffing",
|
|
2267
2264
|
visitorId,
|
|
2268
|
-
path:
|
|
2269
|
-
userAgent:
|
|
2265
|
+
path: ctx.path,
|
|
2266
|
+
userAgent: ctx.headers?.get?.("user-agent") || "",
|
|
2270
2267
|
identifier,
|
|
2271
2268
|
detectionType: "cooldown_active",
|
|
2272
2269
|
detectionLabel: "Credential Stuffing",
|
|
@@ -2282,19 +2279,19 @@ const sentinel = (options) => {
|
|
|
2282
2279
|
}
|
|
2283
2280
|
}
|
|
2284
2281
|
await runSecurityChecks({
|
|
2285
|
-
path:
|
|
2282
|
+
path: ctx.path,
|
|
2286
2283
|
identifier,
|
|
2287
2284
|
visitorId,
|
|
2288
2285
|
identification,
|
|
2289
|
-
userAgent:
|
|
2286
|
+
userAgent: ctx.headers?.get?.("user-agent") || ""
|
|
2290
2287
|
}, securityService, trackEvent, powVerified);
|
|
2291
|
-
if (matchesAnyRoute(
|
|
2288
|
+
if (matchesAnyRoute(ctx.path, [
|
|
2292
2289
|
routes.SIGN_UP_EMAIL,
|
|
2293
2290
|
routes.CHANGE_PASSWORD,
|
|
2294
2291
|
routes.SET_PASSWORD,
|
|
2295
2292
|
routes.RESET_PASSWORD
|
|
2296
2293
|
])) {
|
|
2297
|
-
const body =
|
|
2294
|
+
const body = ctx.body;
|
|
2298
2295
|
const passwordToCheck = body?.newPassword || body?.password;
|
|
2299
2296
|
if (passwordToCheck) {
|
|
2300
2297
|
const compromisedResult = await securityService.checkCompromisedPassword(passwordToCheck);
|
|
@@ -2311,18 +2308,18 @@ const sentinel = (options) => {
|
|
|
2311
2308
|
],
|
|
2312
2309
|
after: [{
|
|
2313
2310
|
matcher: (ctx) => ctx.request?.method !== "GET",
|
|
2314
|
-
handler: createAuthMiddleware(async (
|
|
2315
|
-
const visitorId =
|
|
2316
|
-
const identification =
|
|
2317
|
-
const body =
|
|
2318
|
-
if (matchesAnyRoute(
|
|
2311
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
2312
|
+
const visitorId = ctx.context.visitorId;
|
|
2313
|
+
const identification = ctx.context.identification;
|
|
2314
|
+
const body = ctx.body;
|
|
2315
|
+
if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_EMAIL, routes.SIGN_IN_EMAIL_OTP]) && ctx.context.returned instanceof Error && body?.email && body?.password && visitorId) {
|
|
2319
2316
|
const { email, password } = body;
|
|
2320
2317
|
const ip = identification?.ip || null;
|
|
2321
|
-
|
|
2318
|
+
await ctx.context.runInBackgroundOrAwait(securityService.trackFailedAttempt(email, visitorId, password, ip));
|
|
2322
2319
|
}
|
|
2323
|
-
if (matchesAnyRoute(
|
|
2320
|
+
if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_EMAIL, routes.SIGN_IN_EMAIL_OTP]) && !(ctx.context.returned instanceof Error) && body?.email) {
|
|
2324
2321
|
const email = body.email;
|
|
2325
|
-
|
|
2322
|
+
await ctx.context.runInBackgroundOrAwait(securityService.clearFailedAttempts(email));
|
|
2326
2323
|
}
|
|
2327
2324
|
})
|
|
2328
2325
|
}]
|
|
@@ -2635,6 +2632,17 @@ const initTeamEvents = (tracker) => {
|
|
|
2635
2632
|
//#endregion
|
|
2636
2633
|
//#region src/jwt.ts
|
|
2637
2634
|
/**
|
|
2635
|
+
* Hash the given value
|
|
2636
|
+
* Note: Must match @infra/crypto hash()
|
|
2637
|
+
* @param value - The value to hash
|
|
2638
|
+
*/
|
|
2639
|
+
async function hash(value) {
|
|
2640
|
+
const data = new TextEncoder().encode(value);
|
|
2641
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
2642
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
2643
|
+
return Array.from(hashArray).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2638
2646
|
* Skip JTI check for JWTs issued within this many seconds.
|
|
2639
2647
|
* This is safe because replay attacks require time for interception.
|
|
2640
2648
|
* A freshly issued token is almost certainly legitimate.
|
|
@@ -2695,6 +2703,9 @@ const jwtMiddleware = (options, schema, getJWT) => createAuthMiddleware(async (c
|
|
|
2695
2703
|
}
|
|
2696
2704
|
})).data?.valid) throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
|
|
2697
2705
|
}
|
|
2706
|
+
const apiKeyHash = payload.apiKeyHash;
|
|
2707
|
+
if (typeof apiKeyHash !== "string" || !options.apiKey) throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
|
|
2708
|
+
if (apiKeyHash !== await hash(options.apiKey)) throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
|
|
2698
2709
|
if (schema) {
|
|
2699
2710
|
const parsed = schema.safeParse(payload);
|
|
2700
2711
|
if (!parsed.success) throw ctx.error("UNAUTHORIZED", { message: "Invalid API key" });
|
|
@@ -2705,6 +2716,33 @@ const jwtMiddleware = (options, schema, getJWT) => createAuthMiddleware(async (c
|
|
|
2705
2716
|
|
|
2706
2717
|
//#endregion
|
|
2707
2718
|
//#region src/routes/config.ts
|
|
2719
|
+
const PLUGIN_OPTIONS_EXCLUDE_KEYS = { stripe: new Set(["stripeClient"]) };
|
|
2720
|
+
function isPlainSerializable(value) {
|
|
2721
|
+
if (value === null || typeof value !== "object") return true;
|
|
2722
|
+
if (Array.isArray(value)) return true;
|
|
2723
|
+
if (value instanceof Date) return true;
|
|
2724
|
+
const constructor = Object.getPrototypeOf(value)?.constructor;
|
|
2725
|
+
if (constructor && constructor.name !== "Object" && constructor.name !== "Array") return false;
|
|
2726
|
+
return true;
|
|
2727
|
+
}
|
|
2728
|
+
function sanitizePluginOptions(pluginId, options, seen = /* @__PURE__ */ new WeakSet()) {
|
|
2729
|
+
if (options === null || options === void 0) return options;
|
|
2730
|
+
if (typeof options === "function") return void 0;
|
|
2731
|
+
if (typeof options !== "object") return options;
|
|
2732
|
+
if (seen.has(options)) return void 0;
|
|
2733
|
+
seen.add(options);
|
|
2734
|
+
const excludeKeys = PLUGIN_OPTIONS_EXCLUDE_KEYS[pluginId];
|
|
2735
|
+
if (Array.isArray(options)) return options.map((item) => sanitizePluginOptions(pluginId, item, seen)).filter((item) => item !== void 0);
|
|
2736
|
+
const result = {};
|
|
2737
|
+
for (const [key, value] of Object.entries(options)) {
|
|
2738
|
+
if (excludeKeys?.has(key)) continue;
|
|
2739
|
+
if (typeof value === "function") continue;
|
|
2740
|
+
if (value !== null && typeof value === "object" && !isPlainSerializable(value)) continue;
|
|
2741
|
+
const sanitized = sanitizePluginOptions(pluginId, value, seen);
|
|
2742
|
+
if (sanitized !== void 0) result[key] = sanitized;
|
|
2743
|
+
}
|
|
2744
|
+
return result;
|
|
2745
|
+
}
|
|
2708
2746
|
function estimateEntropy(str) {
|
|
2709
2747
|
const unique = new Set(str).size;
|
|
2710
2748
|
if (unique === 0) return 0;
|
|
@@ -2725,7 +2763,7 @@ const getConfig = (options) => {
|
|
|
2725
2763
|
return {
|
|
2726
2764
|
id: plugin.id,
|
|
2727
2765
|
schema: plugin.schema,
|
|
2728
|
-
options: plugin.options
|
|
2766
|
+
options: sanitizePluginOptions(plugin.id, plugin.options)
|
|
2729
2767
|
};
|
|
2730
2768
|
}),
|
|
2731
2769
|
organization: {
|
|
@@ -4041,12 +4079,19 @@ const listOrganizations = (options) => {
|
|
|
4041
4079
|
"members"
|
|
4042
4080
|
]).optional(),
|
|
4043
4081
|
sortOrder: z$1.enum(["asc", "desc"]).optional(),
|
|
4082
|
+
filterMembers: z$1.enum([
|
|
4083
|
+
"abandoned",
|
|
4084
|
+
"eq1",
|
|
4085
|
+
"gt1",
|
|
4086
|
+
"gt5",
|
|
4087
|
+
"gt10"
|
|
4088
|
+
]).optional(),
|
|
4044
4089
|
search: z$1.string().optional(),
|
|
4045
4090
|
startDate: z$1.date().or(z$1.string().transform((val) => new Date(val))).optional(),
|
|
4046
4091
|
endDate: z$1.date().or(z$1.string().transform((val) => new Date(val))).optional()
|
|
4047
4092
|
}).optional()
|
|
4048
4093
|
}, async (ctx) => {
|
|
4049
|
-
const { limit = 10, offset = 0, sortBy = "createdAt", sortOrder = "desc", search } = ctx.query || {};
|
|
4094
|
+
const { limit = 10, offset = 0, sortBy = "createdAt", sortOrder = "desc", search, filterMembers } = ctx.query || {};
|
|
4050
4095
|
const where = [];
|
|
4051
4096
|
if (search && search.trim().length > 0) {
|
|
4052
4097
|
const searchTerm = search.trim();
|
|
@@ -4072,26 +4117,64 @@ const listOrganizations = (options) => {
|
|
|
4072
4117
|
value: ctx.query.endDate,
|
|
4073
4118
|
operator: "lte"
|
|
4074
4119
|
});
|
|
4120
|
+
const needsInMemoryProcessing = sortBy === "members" || !!filterMembers;
|
|
4121
|
+
const dbSortBy = sortBy === "members" ? "createdAt" : sortBy;
|
|
4122
|
+
const fetchLimit = needsInMemoryProcessing ? 1e3 : limit;
|
|
4123
|
+
const fetchOffset = needsInMemoryProcessing ? 0 : offset;
|
|
4075
4124
|
const [organizations, initialTotal] = await Promise.all([ctx.context.adapter.findMany({
|
|
4076
4125
|
model: "organization",
|
|
4077
4126
|
where,
|
|
4078
|
-
limit,
|
|
4079
|
-
offset,
|
|
4127
|
+
limit: fetchLimit,
|
|
4128
|
+
offset: fetchOffset,
|
|
4080
4129
|
sortBy: {
|
|
4081
|
-
field:
|
|
4130
|
+
field: dbSortBy,
|
|
4082
4131
|
direction: sortOrder
|
|
4083
|
-
}
|
|
4084
|
-
|
|
4085
|
-
}), ctx.context.adapter.count({
|
|
4132
|
+
}
|
|
4133
|
+
}), needsInMemoryProcessing ? Promise.resolve(0) : ctx.context.adapter.count({
|
|
4086
4134
|
model: "organization",
|
|
4087
4135
|
where
|
|
4088
4136
|
})]);
|
|
4089
|
-
const
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4137
|
+
const orgIds = organizations.map((o) => o.id);
|
|
4138
|
+
const allMembers = orgIds.length > 0 ? await ctx.context.adapter.findMany({
|
|
4139
|
+
model: "member",
|
|
4140
|
+
where: [{
|
|
4141
|
+
field: "organizationId",
|
|
4142
|
+
value: orgIds,
|
|
4143
|
+
operator: "in"
|
|
4144
|
+
}]
|
|
4145
|
+
}) : [];
|
|
4146
|
+
const membersByOrg = /* @__PURE__ */ new Map();
|
|
4147
|
+
for (const m of allMembers) {
|
|
4148
|
+
const list = membersByOrg.get(m.organizationId) || [];
|
|
4149
|
+
list.push(m);
|
|
4150
|
+
membersByOrg.set(m.organizationId, list);
|
|
4151
|
+
}
|
|
4152
|
+
let withCounts = organizations.map((organization) => {
|
|
4153
|
+
const orgMembers = membersByOrg.get(organization.id) || [];
|
|
4154
|
+
return {
|
|
4155
|
+
...organization,
|
|
4156
|
+
_members: orgMembers,
|
|
4157
|
+
memberCount: orgMembers.length
|
|
4158
|
+
};
|
|
4159
|
+
});
|
|
4160
|
+
if (filterMembers) {
|
|
4161
|
+
const predicate = {
|
|
4162
|
+
abandoned: (c) => c === 0,
|
|
4163
|
+
eq1: (c) => c === 1,
|
|
4164
|
+
gt1: (c) => c > 1,
|
|
4165
|
+
gt5: (c) => c > 5,
|
|
4166
|
+
gt10: (c) => c > 10
|
|
4167
|
+
}[filterMembers];
|
|
4168
|
+
if (predicate) withCounts = withCounts.filter((o) => predicate(o.memberCount));
|
|
4169
|
+
}
|
|
4170
|
+
if (sortBy === "members") {
|
|
4171
|
+
const dir = sortOrder === "asc" ? 1 : -1;
|
|
4172
|
+
withCounts.sort((a, b) => (a.memberCount - b.memberCount) * dir);
|
|
4173
|
+
}
|
|
4174
|
+
const total = needsInMemoryProcessing ? withCounts.length : initialTotal;
|
|
4175
|
+
if (needsInMemoryProcessing) withCounts = withCounts.slice(offset, offset + limit);
|
|
4093
4176
|
const allUserIds = /* @__PURE__ */ new Set();
|
|
4094
|
-
for (const organization of withCounts) for (const member of organization.
|
|
4177
|
+
for (const organization of withCounts) for (const member of organization._members.slice(0, 5)) allUserIds.add(member.userId);
|
|
4095
4178
|
const users = allUserIds.size > 0 ? await ctx.context.adapter.findMany({
|
|
4096
4179
|
model: "user",
|
|
4097
4180
|
where: [{
|
|
@@ -4103,32 +4186,36 @@ const listOrganizations = (options) => {
|
|
|
4103
4186
|
const userMap = new Map(users.map((u) => [u.id, u]));
|
|
4104
4187
|
return {
|
|
4105
4188
|
organizations: withCounts.map((organization) => {
|
|
4106
|
-
const members = organization.
|
|
4189
|
+
const members = organization._members.slice(0, 5).map((m) => userMap.get(m.userId)).filter((u) => u !== void 0).map((u) => ({
|
|
4107
4190
|
id: u.id,
|
|
4108
4191
|
name: u.name,
|
|
4109
4192
|
email: u.email,
|
|
4110
4193
|
image: u.image
|
|
4111
4194
|
}));
|
|
4195
|
+
const { _members, ...org } = organization;
|
|
4112
4196
|
return {
|
|
4113
|
-
...
|
|
4197
|
+
...org,
|
|
4114
4198
|
members
|
|
4115
4199
|
};
|
|
4116
4200
|
}),
|
|
4117
|
-
total
|
|
4201
|
+
total,
|
|
4118
4202
|
offset,
|
|
4119
4203
|
limit
|
|
4120
4204
|
};
|
|
4121
4205
|
});
|
|
4122
4206
|
};
|
|
4207
|
+
function parseWhereClause$1(val) {
|
|
4208
|
+
if (!val) return [];
|
|
4209
|
+
const parsed = JSON.parse(val);
|
|
4210
|
+
if (!Array.isArray(parsed)) return [];
|
|
4211
|
+
return parsed;
|
|
4212
|
+
}
|
|
4123
4213
|
const exportOrganizationsQuerySchema = z$1.object({
|
|
4124
4214
|
limit: z$1.number().or(z$1.string().transform(Number)).optional(),
|
|
4125
4215
|
offset: z$1.number().or(z$1.string().transform(Number)).optional(),
|
|
4126
4216
|
sortBy: z$1.string().optional(),
|
|
4127
4217
|
sortOrder: z$1.enum(["asc", "desc"]).optional(),
|
|
4128
|
-
where: z$1.string().transform((
|
|
4129
|
-
if (!val) return [];
|
|
4130
|
-
return JSON.parse(val);
|
|
4131
|
-
}).optional()
|
|
4218
|
+
where: z$1.string().transform(parseWhereClause$1).optional()
|
|
4132
4219
|
}).optional();
|
|
4133
4220
|
const exportOrganizations = (options) => {
|
|
4134
4221
|
return createAuthEndpoint("/dash/export-organizations", {
|
|
@@ -5149,7 +5236,7 @@ const updateOrganization = (options) => {
|
|
|
5149
5236
|
value: organizationId
|
|
5150
5237
|
}],
|
|
5151
5238
|
update: {
|
|
5152
|
-
updateData,
|
|
5239
|
+
...updateData,
|
|
5153
5240
|
metadata: typeof updateData.metadata === "object" ? JSON.stringify(updateData.metadata) : updateData.metadata
|
|
5154
5241
|
}
|
|
5155
5242
|
});
|
|
@@ -6121,19 +6208,19 @@ const revokeManySessions = (options) => createAuthEndpoint("/dash/sessions/revok
|
|
|
6121
6208
|
|
|
6122
6209
|
//#endregion
|
|
6123
6210
|
//#region src/routes/users.ts
|
|
6211
|
+
function parseWhereClause(val) {
|
|
6212
|
+
if (!val) return [];
|
|
6213
|
+
const parsed = JSON.parse(val);
|
|
6214
|
+
if (!Array.isArray(parsed)) return [];
|
|
6215
|
+
return parsed;
|
|
6216
|
+
}
|
|
6124
6217
|
const getUsersQuerySchema = z$1.object({
|
|
6125
6218
|
limit: z$1.number().or(z$1.string().transform(Number)).optional(),
|
|
6126
6219
|
offset: z$1.number().or(z$1.string().transform(Number)).optional(),
|
|
6127
6220
|
sortBy: z$1.string().optional(),
|
|
6128
6221
|
sortOrder: z$1.enum(["asc", "desc"]).optional(),
|
|
6129
|
-
where: z$1.string().transform((
|
|
6130
|
-
|
|
6131
|
-
return JSON.parse(val);
|
|
6132
|
-
}).optional(),
|
|
6133
|
-
countWhere: z$1.string().transform((val) => {
|
|
6134
|
-
if (!val) return [];
|
|
6135
|
-
return JSON.parse(val);
|
|
6136
|
-
}).optional()
|
|
6222
|
+
where: z$1.string().transform(parseWhereClause).optional(),
|
|
6223
|
+
countWhere: z$1.string().transform(parseWhereClause).optional()
|
|
6137
6224
|
}).optional();
|
|
6138
6225
|
const getUsers = (options) => {
|
|
6139
6226
|
return createAuthEndpoint("/dash/list-users", {
|
|
@@ -6148,6 +6235,8 @@ const getUsers = (options) => {
|
|
|
6148
6235
|
if (fields.lastActiveAt.type !== "date") return false;
|
|
6149
6236
|
return true;
|
|
6150
6237
|
})();
|
|
6238
|
+
const where = ctx.query?.where?.length ? ctx.query.where : void 0;
|
|
6239
|
+
const countWhere = ctx.query?.countWhere?.length ? ctx.query.countWhere : void 0;
|
|
6151
6240
|
const userQuery = ctx.context.adapter.findMany({
|
|
6152
6241
|
model: "user",
|
|
6153
6242
|
limit: ctx.query?.limit || 10,
|
|
@@ -6156,11 +6245,11 @@ const getUsers = (options) => {
|
|
|
6156
6245
|
field: ctx.query?.sortBy || "createdAt",
|
|
6157
6246
|
direction: ctx.query?.sortOrder || "desc"
|
|
6158
6247
|
},
|
|
6159
|
-
where
|
|
6248
|
+
where
|
|
6160
6249
|
});
|
|
6161
6250
|
const totalQuery = ctx.context.adapter.count({
|
|
6162
6251
|
model: "user",
|
|
6163
|
-
where:
|
|
6252
|
+
where: countWhere
|
|
6164
6253
|
});
|
|
6165
6254
|
const onlineUsersQuery = activityTrackingEnabled ? ctx.context.adapter.count({
|
|
6166
6255
|
model: "user",
|
|
@@ -6169,6 +6258,9 @@ const getUsers = (options) => {
|
|
|
6169
6258
|
value: /* @__PURE__ */ new Date(Date.now() - 1e3 * 60 * 2),
|
|
6170
6259
|
operator: "gte"
|
|
6171
6260
|
}]
|
|
6261
|
+
}).catch((e) => {
|
|
6262
|
+
ctx.context.logger.error("[Dash] Failed to count online users:", e);
|
|
6263
|
+
return 0;
|
|
6172
6264
|
}) : Promise.resolve(0);
|
|
6173
6265
|
const [users, total, onlineUsers] = await Promise.all([
|
|
6174
6266
|
userQuery,
|
|
@@ -7387,12 +7479,12 @@ async function sha256(message) {
|
|
|
7387
7479
|
/**
|
|
7388
7480
|
* Check if a hash has the required number of leading zero bits
|
|
7389
7481
|
*/
|
|
7390
|
-
function hasLeadingZeroBits(hash, bits) {
|
|
7482
|
+
function hasLeadingZeroBits(hash$1, bits) {
|
|
7391
7483
|
const fullHexChars = Math.floor(bits / 4);
|
|
7392
7484
|
const remainingBits = bits % 4;
|
|
7393
|
-
for (let i = 0; i < fullHexChars; i++) if (hash[i] !== "0") return false;
|
|
7394
|
-
if (remainingBits > 0 && fullHexChars < hash.length) {
|
|
7395
|
-
if (parseInt(hash[fullHexChars], 16) > (1 << 4 - remainingBits) - 1) return false;
|
|
7485
|
+
for (let i = 0; i < fullHexChars; i++) if (hash$1[i] !== "0") return false;
|
|
7486
|
+
if (remainingBits > 0 && fullHexChars < hash$1.length) {
|
|
7487
|
+
if (parseInt(hash$1[fullHexChars], 16) > (1 << 4 - remainingBits) - 1) return false;
|
|
7396
7488
|
}
|
|
7397
7489
|
return true;
|
|
7398
7490
|
}
|
|
@@ -7692,8 +7784,10 @@ const dash = (options) => {
|
|
|
7692
7784
|
const ctx$1 = _ctx;
|
|
7693
7785
|
if (!ctx$1 || !session.userId) return;
|
|
7694
7786
|
const location = ctx$1.context.location;
|
|
7787
|
+
const loginMethod = getLoginMethod(ctx$1) ?? void 0;
|
|
7695
7788
|
const enrichedSession = {
|
|
7696
7789
|
...session,
|
|
7790
|
+
loginMethod,
|
|
7697
7791
|
ipAddress: location?.ipAddress,
|
|
7698
7792
|
city: location?.city,
|
|
7699
7793
|
country: location?.country,
|
|
@@ -7812,11 +7906,19 @@ const dash = (options) => {
|
|
|
7812
7906
|
},
|
|
7813
7907
|
hooks: {
|
|
7814
7908
|
before: [{
|
|
7815
|
-
matcher: (ctx) =>
|
|
7909
|
+
matcher: (ctx) => {
|
|
7910
|
+
if (ctx.request?.method !== "GET") return true;
|
|
7911
|
+
const path = new URL(ctx.request.url).pathname;
|
|
7912
|
+
return matchesAnyRoute(path, [routes.SIGN_IN_SOCIAL_CALLBACK, routes.SIGN_IN_OAUTH_CALLBACK]);
|
|
7913
|
+
},
|
|
7816
7914
|
handler: createIdentificationMiddleware(opts)
|
|
7817
7915
|
}],
|
|
7818
7916
|
after: [{
|
|
7819
|
-
matcher: (ctx) =>
|
|
7917
|
+
matcher: (ctx) => {
|
|
7918
|
+
if (ctx.request?.method !== "GET") return true;
|
|
7919
|
+
const path = new URL(ctx.request.url).pathname;
|
|
7920
|
+
return matchesAnyRoute(path, [routes.SIGN_IN_SOCIAL_CALLBACK, routes.SIGN_IN_OAUTH_CALLBACK]);
|
|
7921
|
+
},
|
|
7820
7922
|
handler: createAuthMiddleware(async (_ctx) => {
|
|
7821
7923
|
const ctx = _ctx;
|
|
7822
7924
|
const trigger = getTriggerInfo(ctx, ctx.context.session?.user.id ?? UNKNOWN_USER);
|
|
@@ -7825,6 +7927,17 @@ const dash = (options) => {
|
|
|
7825
7927
|
if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_EMAIL, routes.SIGN_IN_EMAIL_OTP]) && ctx.context.returned instanceof Error && body?.email) trackEmailSignInAttempt(ctx, trigger);
|
|
7826
7928
|
if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL]) && ctx.context.returned instanceof Error && ctx.body.provider && ctx.body.idToken) trackSocialSignInAttempt(ctx, trigger);
|
|
7827
7929
|
if (matchesAnyRoute(ctx.path, [routes.SIGN_IN_SOCIAL_CALLBACK]) && ctx.context.returned instanceof Error) trackSocialSignInRedirectionAttempt(ctx, trigger);
|
|
7930
|
+
const headerRequestId = ctx.request?.headers.get("X-Request-Id");
|
|
7931
|
+
if (headerRequestId) ctx.setCookie(IDENTIFICATION_COOKIE_NAME, headerRequestId, {
|
|
7932
|
+
maxAge: 600,
|
|
7933
|
+
sameSite: "lax",
|
|
7934
|
+
httpOnly: true,
|
|
7935
|
+
path: "/"
|
|
7936
|
+
});
|
|
7937
|
+
else if (ctx.context.requestId) ctx.setCookie(IDENTIFICATION_COOKIE_NAME, "", {
|
|
7938
|
+
maxAge: 0,
|
|
7939
|
+
path: "/"
|
|
7940
|
+
});
|
|
7828
7941
|
})
|
|
7829
7942
|
}, {
|
|
7830
7943
|
handler: createAuthMiddleware(async (ctx) => {
|
|
@@ -7933,7 +8046,7 @@ const dash = (options) => {
|
|
|
7933
8046
|
getDashDirectoryDetails: getDirectoryDetails(opts),
|
|
7934
8047
|
dashExecuteAdapter: executeAdapter(opts)
|
|
7935
8048
|
},
|
|
7936
|
-
schema:
|
|
8049
|
+
schema: opts.activityTracking?.enabled ? { user: { fields: { lastActiveAt: { type: "date" } } } } : {}
|
|
7937
8050
|
};
|
|
7938
8051
|
};
|
|
7939
8052
|
|