@better-auth/infra 0.1.14 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/client.d.mts +2 -70
- package/dist/client.mjs +1 -87
- package/dist/dash-client-hJHp7l_X.d.mts +72 -0
- package/dist/index.d.mts +306 -334
- package/dist/index.mjs +280 -70
- package/dist/native.d.mts +18 -0
- package/dist/native.mjs +292 -0
- package/dist/pow-BUuN_EKw.mjs +131 -0
- package/package.json +28 -3
- package/dist/identification-DF2nvmng.mjs +0 -178
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { n as INFRA_KV_URL, r as KV_TIMEOUT_MS, t as INFRA_API_URL } from "./constants-DdWGfvz1.mjs";
|
|
2
|
-
import { n as createIdentificationMiddleware, t as IDENTIFICATION_COOKIE_NAME } from "./identification-DF2nvmng.mjs";
|
|
3
2
|
import { EMAIL_TEMPLATES, createEmailSender, sendBulkEmails, sendEmail } from "./email.mjs";
|
|
4
3
|
import { APIError, generateId, getAuthTables, logger, parseState } from "better-auth";
|
|
5
4
|
import { env } from "@better-auth/core/env";
|
|
@@ -10,7 +9,6 @@ import { isValidPhoneNumber, parsePhoneNumberFromString } from "libphonenumber-j
|
|
|
10
9
|
import { createLocalJWKSet, jwtVerify } from "jose";
|
|
11
10
|
import z$1, { z } from "zod";
|
|
12
11
|
import { setSessionCookie } from "better-auth/cookies";
|
|
13
|
-
import { DEFAULT_MAX_SAML_METADATA_SIZE, DigestAlgorithm, DiscoveryError, SignatureAlgorithm, discoverOIDCConfig } from "@better-auth/sso";
|
|
14
12
|
//#region src/options.ts
|
|
15
13
|
function resolveConnectionOptions(options) {
|
|
16
14
|
return {
|
|
@@ -853,6 +851,152 @@ const initTrackEvents = (options) => {
|
|
|
853
851
|
return { tracker: { trackEvent } };
|
|
854
852
|
};
|
|
855
853
|
//#endregion
|
|
854
|
+
//#region src/identification.ts
|
|
855
|
+
/**
|
|
856
|
+
* Identification Service
|
|
857
|
+
*
|
|
858
|
+
* Fetches identification data from the durable-kv service
|
|
859
|
+
* when a request includes an X-Request-Id header.
|
|
860
|
+
*/
|
|
861
|
+
const IDENTIFICATION_COOKIE_NAME = "__infra-rid";
|
|
862
|
+
const identificationCache = /* @__PURE__ */ new Map();
|
|
863
|
+
const CACHE_TTL_MS = 6e4;
|
|
864
|
+
const CACHE_MAX_SIZE = 1e3;
|
|
865
|
+
let lastCleanup = Date.now();
|
|
866
|
+
function cleanupCache() {
|
|
867
|
+
const now = Date.now();
|
|
868
|
+
for (const [key, value] of identificationCache.entries()) if (now - value.timestamp > CACHE_TTL_MS) identificationCache.delete(key);
|
|
869
|
+
lastCleanup = now;
|
|
870
|
+
}
|
|
871
|
+
function maybeCleanup() {
|
|
872
|
+
if (Date.now() - lastCleanup > CACHE_TTL_MS || identificationCache.size > CACHE_MAX_SIZE) cleanupCache();
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Fetch identification data from durable-kv by requestId
|
|
876
|
+
*/
|
|
877
|
+
async function getIdentification(requestId, apiKey, kvUrl) {
|
|
878
|
+
maybeCleanup();
|
|
879
|
+
const cached = identificationCache.get(requestId);
|
|
880
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) return cached.data;
|
|
881
|
+
const baseUrl = kvUrl || INFRA_KV_URL;
|
|
882
|
+
const maxRetries = 3;
|
|
883
|
+
const retryDelays = [
|
|
884
|
+
50,
|
|
885
|
+
100,
|
|
886
|
+
200
|
|
887
|
+
];
|
|
888
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
889
|
+
const response = await fetch(`${baseUrl}/identify/${requestId}`, {
|
|
890
|
+
method: "GET",
|
|
891
|
+
headers: { "x-api-key": apiKey },
|
|
892
|
+
signal: AbortSignal.timeout(KV_TIMEOUT_MS)
|
|
893
|
+
});
|
|
894
|
+
if (response.ok) {
|
|
895
|
+
const data = await response.json();
|
|
896
|
+
identificationCache.set(requestId, {
|
|
897
|
+
data,
|
|
898
|
+
timestamp: Date.now()
|
|
899
|
+
});
|
|
900
|
+
return data;
|
|
901
|
+
}
|
|
902
|
+
if (response.status === 404 && attempt < maxRetries) {
|
|
903
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelays[attempt]));
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if (response.status !== 404) identificationCache.set(requestId, {
|
|
907
|
+
data: null,
|
|
908
|
+
timestamp: Date.now()
|
|
909
|
+
});
|
|
910
|
+
return null;
|
|
911
|
+
} catch (error) {
|
|
912
|
+
if (attempt === maxRetries) {
|
|
913
|
+
logger.error("[Dash] Failed to fetch identification:", error);
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelays[attempt] || 50));
|
|
917
|
+
}
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Extract identification headers from a request
|
|
922
|
+
*/
|
|
923
|
+
function extractIdentificationHeaders(request) {
|
|
924
|
+
if (!request) return {
|
|
925
|
+
visitorId: null,
|
|
926
|
+
requestId: null
|
|
927
|
+
};
|
|
928
|
+
return {
|
|
929
|
+
visitorId: request.headers.get("X-Visitor-Id"),
|
|
930
|
+
requestId: request.headers.get("X-Request-Id")
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Early middleware that loads identification data
|
|
935
|
+
*/
|
|
936
|
+
function createIdentificationMiddleware(options) {
|
|
937
|
+
return createAuthMiddleware(async (ctx) => {
|
|
938
|
+
const { visitorId, requestId: headerRequestId } = extractIdentificationHeaders(ctx.request);
|
|
939
|
+
const requestId = headerRequestId ?? ctx.getCookie("__infra-rid") ?? null;
|
|
940
|
+
ctx.context.visitorId = visitorId;
|
|
941
|
+
ctx.context.requestId = requestId;
|
|
942
|
+
if (requestId) ctx.context.identification = ctx.context.identification ?? await getIdentification(requestId, options.apiKey, options.kvUrl) ?? null;
|
|
943
|
+
else ctx.context.identification = null;
|
|
944
|
+
const ipConfig = ctx.context.options?.advanced?.ipAddress;
|
|
945
|
+
if (ipConfig?.disableIpTracking === true) {
|
|
946
|
+
ctx.context.location = void 0;
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const identification = ctx.context.identification;
|
|
950
|
+
if (requestId && identification) {
|
|
951
|
+
const loc = getLocation(identification);
|
|
952
|
+
ctx.context.location = {
|
|
953
|
+
ipAddress: identification.ip || void 0,
|
|
954
|
+
city: loc?.city || void 0,
|
|
955
|
+
country: loc?.country?.name || void 0,
|
|
956
|
+
countryCode: loc?.country?.code || void 0
|
|
957
|
+
};
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
const ipAddress = getClientIpFromRequest(ctx.request, ipConfig?.ipAddressHeaders || null);
|
|
961
|
+
const countryCode = getCountryCodeFromRequest(ctx.request);
|
|
962
|
+
if (ipAddress || countryCode) {
|
|
963
|
+
ctx.context.location = {
|
|
964
|
+
ipAddress,
|
|
965
|
+
countryCode
|
|
966
|
+
};
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
ctx.context.location = void 0;
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Get the visitor's location
|
|
974
|
+
*/
|
|
975
|
+
function getLocation(identification) {
|
|
976
|
+
if (!identification) return null;
|
|
977
|
+
return identification.location;
|
|
978
|
+
}
|
|
979
|
+
function getClientIpFromRequest(request, ipAddressHeaders) {
|
|
980
|
+
if (!request) return void 0;
|
|
981
|
+
const headers = ipAddressHeaders?.length ? ipAddressHeaders : [
|
|
982
|
+
"cf-connecting-ip",
|
|
983
|
+
"x-forwarded-for",
|
|
984
|
+
"x-real-ip",
|
|
985
|
+
"x-vercel-forwarded-for"
|
|
986
|
+
];
|
|
987
|
+
for (const headerName of headers) {
|
|
988
|
+
const value = request.headers.get(headerName);
|
|
989
|
+
if (!value) continue;
|
|
990
|
+
const ip = value.split(",")[0]?.trim();
|
|
991
|
+
if (ip) return ip;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function getCountryCodeFromRequest(request) {
|
|
995
|
+
if (!request) return void 0;
|
|
996
|
+
const cc = request.headers.get("cf-ipcountry") ?? request.headers.get("x-vercel-ip-country");
|
|
997
|
+
return cc ? cc.toUpperCase() : void 0;
|
|
998
|
+
}
|
|
999
|
+
//#endregion
|
|
856
1000
|
//#region src/validation/matchers.ts
|
|
857
1001
|
const paths = [
|
|
858
1002
|
"/sign-up/email",
|
|
@@ -2602,7 +2746,7 @@ const jwtValidateMiddleware = (options) => createAuthMiddleware(async (ctx) => {
|
|
|
2602
2746
|
});
|
|
2603
2747
|
//#endregion
|
|
2604
2748
|
//#region src/version.ts
|
|
2605
|
-
const PLUGIN_VERSION = "0.1
|
|
2749
|
+
const PLUGIN_VERSION = "0.2.1";
|
|
2606
2750
|
//#endregion
|
|
2607
2751
|
//#region src/routes/auth/config.ts
|
|
2608
2752
|
const PLUGIN_OPTIONS_EXCLUDE_KEYS = { stripe: new Set(["stripeClient"]) };
|
|
@@ -2655,7 +2799,7 @@ const getConfig = (options) => {
|
|
|
2655
2799
|
version: plugin.version,
|
|
2656
2800
|
options: sanitizePluginOptions(plugin.id, plugin.options)
|
|
2657
2801
|
};
|
|
2658
|
-
if (plugin.id === "dash") return {
|
|
2802
|
+
if (plugin.id === "dash" && !plugin.version) return {
|
|
2659
2803
|
...base,
|
|
2660
2804
|
version: PLUGIN_VERSION
|
|
2661
2805
|
};
|
|
@@ -4089,11 +4233,13 @@ const listOrganizations = (options) => {
|
|
|
4089
4233
|
field: "name",
|
|
4090
4234
|
value: searchTerm,
|
|
4091
4235
|
operator: "starts_with",
|
|
4236
|
+
mode: "insensitive",
|
|
4092
4237
|
connector: "OR"
|
|
4093
4238
|
}, {
|
|
4094
4239
|
field: "slug",
|
|
4095
4240
|
value: searchTerm,
|
|
4096
4241
|
operator: "starts_with",
|
|
4242
|
+
mode: "insensitive",
|
|
4097
4243
|
connector: "OR"
|
|
4098
4244
|
});
|
|
4099
4245
|
}
|
|
@@ -4109,7 +4255,7 @@ const listOrganizations = (options) => {
|
|
|
4109
4255
|
});
|
|
4110
4256
|
const needsInMemoryProcessing = sortBy === "members" || !!filterMembers;
|
|
4111
4257
|
const dbSortBy = sortBy === "members" ? "createdAt" : sortBy;
|
|
4112
|
-
const fetchLimit = needsInMemoryProcessing ?
|
|
4258
|
+
const fetchLimit = needsInMemoryProcessing ? 2500 : limit;
|
|
4113
4259
|
const fetchOffset = needsInMemoryProcessing ? 0 : offset;
|
|
4114
4260
|
const [organizations, initialTotal] = await Promise.all([ctx.context.adapter.findMany({
|
|
4115
4261
|
model: "organization",
|
|
@@ -5254,8 +5400,16 @@ function getSSOPlugin(ctx) {
|
|
|
5254
5400
|
}
|
|
5255
5401
|
//#endregion
|
|
5256
5402
|
//#region src/routes/sso-validation.ts
|
|
5257
|
-
|
|
5258
|
-
|
|
5403
|
+
/**
|
|
5404
|
+
* SAML metadata limits and algorithm URIs aligned with @better-auth/sso
|
|
5405
|
+
* (see packages/sso dist constants). Inlined so consumers (e.g. Metro) never
|
|
5406
|
+
* statically pull @better-auth/sso for validation-only code paths.
|
|
5407
|
+
*/
|
|
5408
|
+
const DEFAULT_MAX_SAML_METADATA_SIZE = 100 * 1024;
|
|
5409
|
+
const RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
|
|
5410
|
+
const SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1";
|
|
5411
|
+
const DEPRECATED_SIGNATURE_ALGORITHMS = [RSA_SHA1];
|
|
5412
|
+
const DEPRECATED_DIGEST_ALGORITHMS = [SHA1];
|
|
5259
5413
|
function validateSAMLMetadataSize(metadataXml, maxSize = DEFAULT_MAX_SAML_METADATA_SIZE) {
|
|
5260
5414
|
if (new TextEncoder().encode(metadataXml).byteLength > maxSize) throw new Error(`IdP metadata exceeds maximum allowed size (${Math.round(maxSize / 1024)}KB)`);
|
|
5261
5415
|
}
|
|
@@ -5283,6 +5437,10 @@ function validateSAMLMetadataAlgorithms(metadataXml) {
|
|
|
5283
5437
|
}
|
|
5284
5438
|
//#endregion
|
|
5285
5439
|
//#region src/routes/sso/index.ts
|
|
5440
|
+
let ssoRuntimeModule;
|
|
5441
|
+
function loadSsoRuntime() {
|
|
5442
|
+
return ssoRuntimeModule ??= import("@better-auth/sso");
|
|
5443
|
+
}
|
|
5286
5444
|
function requireOrganizationAccess(ctx) {
|
|
5287
5445
|
const orgIdFromUrl = tryDecode(ctx.params.id);
|
|
5288
5446
|
const orgIdFromToken = ctx.context.payload?.organizationId;
|
|
@@ -5355,6 +5513,7 @@ async function resolveSAMLConfig(samlConfig, providerId, baseURL, ctx) {
|
|
|
5355
5513
|
}] } : {},
|
|
5356
5514
|
...samlConfig.cert ? { cert: samlConfig.cert } : {}
|
|
5357
5515
|
};
|
|
5516
|
+
const m = samlConfig.mapping;
|
|
5358
5517
|
return {
|
|
5359
5518
|
config: {
|
|
5360
5519
|
issuer: samlConfig.entityId ?? `${baseURL}/sso/saml2/sp/metadata?providerId=${providerId}`,
|
|
@@ -5363,7 +5522,15 @@ async function resolveSAMLConfig(samlConfig, providerId, baseURL, ctx) {
|
|
|
5363
5522
|
spMetadata: {},
|
|
5364
5523
|
entryPoint: samlConfig.entryPoint ?? "",
|
|
5365
5524
|
cert: samlConfig.cert ?? "",
|
|
5366
|
-
...
|
|
5525
|
+
...m ? { mapping: {
|
|
5526
|
+
id: m.id ?? "nameID",
|
|
5527
|
+
email: m.email ?? "email",
|
|
5528
|
+
name: m.name ?? "name",
|
|
5529
|
+
emailVerified: m.emailVerified,
|
|
5530
|
+
firstName: m.firstName,
|
|
5531
|
+
lastName: m.lastName,
|
|
5532
|
+
extraFields: m.extraFields
|
|
5533
|
+
} } : {}
|
|
5367
5534
|
},
|
|
5368
5535
|
...metadataAlgorithmWarnings.length > 0 ? { warnings: metadataAlgorithmWarnings } : {}
|
|
5369
5536
|
};
|
|
@@ -5377,8 +5544,9 @@ async function resolveOIDCConfig(oidcConfig, domain, ctx) {
|
|
|
5377
5544
|
}
|
|
5378
5545
|
const issuerHint = oidcConfig.issuer || `https://${normalizedDomain}`;
|
|
5379
5546
|
const issuer = issuerHint.startsWith("http") ? issuerHint : `https://${issuerHint}`;
|
|
5547
|
+
const sso = await loadSsoRuntime();
|
|
5380
5548
|
try {
|
|
5381
|
-
const hydratedConfig = await discoverOIDCConfig({
|
|
5549
|
+
const hydratedConfig = await sso.discoverOIDCConfig({
|
|
5382
5550
|
issuer,
|
|
5383
5551
|
discoveryEndpoint: oidcConfig.discoveryUrl,
|
|
5384
5552
|
isTrustedOrigin: (url) => {
|
|
@@ -5390,6 +5558,7 @@ async function resolveOIDCConfig(oidcConfig, domain, ctx) {
|
|
|
5390
5558
|
}
|
|
5391
5559
|
}
|
|
5392
5560
|
});
|
|
5561
|
+
const om = oidcConfig.mapping;
|
|
5393
5562
|
return {
|
|
5394
5563
|
config: {
|
|
5395
5564
|
clientId: oidcConfig.clientId,
|
|
@@ -5402,12 +5571,19 @@ async function resolveOIDCConfig(oidcConfig, domain, ctx) {
|
|
|
5402
5571
|
userInfoEndpoint: hydratedConfig.userInfoEndpoint,
|
|
5403
5572
|
tokenEndpointAuthentication: hydratedConfig.tokenEndpointAuthentication,
|
|
5404
5573
|
pkce: true,
|
|
5405
|
-
...
|
|
5574
|
+
...om ? { mapping: {
|
|
5575
|
+
id: om.id ?? "sub",
|
|
5576
|
+
email: om.email ?? "email",
|
|
5577
|
+
name: om.name ?? "name",
|
|
5578
|
+
emailVerified: om.emailVerified,
|
|
5579
|
+
image: om.image,
|
|
5580
|
+
extraFields: om.extraFields
|
|
5581
|
+
} } : {}
|
|
5406
5582
|
},
|
|
5407
5583
|
issuer: hydratedConfig.issuer
|
|
5408
5584
|
};
|
|
5409
5585
|
} catch (e) {
|
|
5410
|
-
if (e instanceof DiscoveryError) {
|
|
5586
|
+
if (e instanceof sso.DiscoveryError) {
|
|
5411
5587
|
ctx.context.logger.error("[Dash] OIDC discovery failed:", e);
|
|
5412
5588
|
throw ctx.error("BAD_REQUEST", {
|
|
5413
5589
|
message: `OIDC discovery failed: ${e.message}`,
|
|
@@ -5494,6 +5670,8 @@ const createSsoProvider = (options) => {
|
|
|
5494
5670
|
...buildSessionContext(userId)
|
|
5495
5671
|
}
|
|
5496
5672
|
});
|
|
5673
|
+
let verificationToken = null;
|
|
5674
|
+
if ("domainVerificationToken" in result && typeof result.domainVerificationToken === "string") verificationToken = result.domainVerificationToken;
|
|
5497
5675
|
return {
|
|
5498
5676
|
success: true,
|
|
5499
5677
|
provider: {
|
|
@@ -5503,7 +5681,7 @@ const createSsoProvider = (options) => {
|
|
|
5503
5681
|
},
|
|
5504
5682
|
domainVerification: {
|
|
5505
5683
|
txtRecordName: `better-auth-token-${providerId}`,
|
|
5506
|
-
verificationToken
|
|
5684
|
+
verificationToken
|
|
5507
5685
|
}
|
|
5508
5686
|
};
|
|
5509
5687
|
} catch (e) {
|
|
@@ -5604,7 +5782,9 @@ const requestSsoVerificationToken = (options) => {
|
|
|
5604
5782
|
requireOrganizationPlugin(ctx);
|
|
5605
5783
|
requireOrganizationAccess(ctx);
|
|
5606
5784
|
const ssoPlugin = getSSOPlugin(ctx);
|
|
5607
|
-
if (!ssoPlugin
|
|
5785
|
+
if (!ssoPlugin || !ssoPlugin.options?.domainVerification?.enabled) throw ctx.error("BAD_REQUEST", { message: "SSO plugin with domain verification is not enabled or feature is not supported in your plugin version" });
|
|
5786
|
+
const endpoints = ssoPlugin.endpoints;
|
|
5787
|
+
if (typeof endpoints.requestDomainVerification !== "function") throw ctx.error("BAD_REQUEST", { message: "SSO plugin with domain verification is not enabled or feature is not supported in your plugin version" });
|
|
5608
5788
|
const organizationId = tryDecode(ctx.params.id);
|
|
5609
5789
|
const { providerId } = ctx.body;
|
|
5610
5790
|
const provider = await ctx.context.adapter.findOne({
|
|
@@ -5620,7 +5800,7 @@ const requestSsoVerificationToken = (options) => {
|
|
|
5620
5800
|
if (!provider) throw ctx.error("NOT_FOUND", { message: "SSO provider not found" });
|
|
5621
5801
|
const txtRecordName = `${ssoPlugin.options?.domainVerification?.tokenPrefix || "better-auth-token"}-${provider.providerId}`;
|
|
5622
5802
|
try {
|
|
5623
|
-
const result = await
|
|
5803
|
+
const result = await endpoints.requestDomainVerification({
|
|
5624
5804
|
body: { providerId },
|
|
5625
5805
|
context: {
|
|
5626
5806
|
...ctx.context,
|
|
@@ -5650,7 +5830,9 @@ const verifySsoProviderDomain = (options) => {
|
|
|
5650
5830
|
requireOrganizationPlugin(ctx);
|
|
5651
5831
|
requireOrganizationAccess(ctx);
|
|
5652
5832
|
const ssoPlugin = getSSOPlugin(ctx);
|
|
5653
|
-
if (!ssoPlugin
|
|
5833
|
+
if (!ssoPlugin || !ssoPlugin.options?.domainVerification?.enabled) throw ctx.error("BAD_REQUEST", { message: "SSO plugin with domain verification is not enabled or feature is not supported in your plugin version" });
|
|
5834
|
+
const dvEndpoints = ssoPlugin.endpoints;
|
|
5835
|
+
if (typeof dvEndpoints.verifyDomain !== "function") throw ctx.error("BAD_REQUEST", { message: "SSO plugin with domain verification is not enabled or feature is not supported in your plugin version" });
|
|
5654
5836
|
const organizationId = tryDecode(ctx.params.id);
|
|
5655
5837
|
const { providerId } = ctx.body;
|
|
5656
5838
|
const provider = await ctx.context.adapter.findOne({
|
|
@@ -5665,7 +5847,7 @@ const verifySsoProviderDomain = (options) => {
|
|
|
5665
5847
|
});
|
|
5666
5848
|
if (!provider) throw ctx.error("NOT_FOUND", { message: "SSO provider not found" });
|
|
5667
5849
|
try {
|
|
5668
|
-
await
|
|
5850
|
+
await dvEndpoints.verifyDomain({
|
|
5669
5851
|
body: { providerId },
|
|
5670
5852
|
context: {
|
|
5671
5853
|
...ctx.context,
|
|
@@ -6443,7 +6625,7 @@ async function countUniqueActiveUsers(ctx, range, options) {
|
|
|
6443
6625
|
break;
|
|
6444
6626
|
}
|
|
6445
6627
|
}
|
|
6446
|
-
}, { concurrency:
|
|
6628
|
+
}, { concurrency: 10 });
|
|
6447
6629
|
return activeUserIds.size;
|
|
6448
6630
|
}
|
|
6449
6631
|
const sessions = await ctx.context.adapter.findMany({
|
|
@@ -6453,6 +6635,34 @@ async function countUniqueActiveUsers(ctx, range, options) {
|
|
|
6453
6635
|
});
|
|
6454
6636
|
return new Set(sessions.map((s) => s.userId)).size;
|
|
6455
6637
|
}
|
|
6638
|
+
function optionalQuery(ctx, label, getCount) {
|
|
6639
|
+
return async function runQuery() {
|
|
6640
|
+
try {
|
|
6641
|
+
return {
|
|
6642
|
+
ok: true,
|
|
6643
|
+
value: await getCount()
|
|
6644
|
+
};
|
|
6645
|
+
} catch (error) {
|
|
6646
|
+
ctx.context.logger.warn(`[Dash] User stats query "${label}" failed`, error);
|
|
6647
|
+
return {
|
|
6648
|
+
ok: false,
|
|
6649
|
+
value: null
|
|
6650
|
+
};
|
|
6651
|
+
}
|
|
6652
|
+
};
|
|
6653
|
+
}
|
|
6654
|
+
function signUpPeriod(current, previous, calculatePercentage) {
|
|
6655
|
+
return {
|
|
6656
|
+
signUps: current.ok ? current.value : null,
|
|
6657
|
+
percentage: current.ok && previous.ok ? calculatePercentage(current.value, previous.value) : null
|
|
6658
|
+
};
|
|
6659
|
+
}
|
|
6660
|
+
function activePeriod(current, previous, calculatePercentage) {
|
|
6661
|
+
return {
|
|
6662
|
+
active: current.ok ? current.value : null,
|
|
6663
|
+
percentage: current.ok && previous.ok ? calculatePercentage(current.value, previous.value) : null
|
|
6664
|
+
};
|
|
6665
|
+
}
|
|
6456
6666
|
const getUserStats = (options) => createAuthEndpoint("/dash/user-stats", {
|
|
6457
6667
|
method: "GET",
|
|
6458
6668
|
use: [jwtMiddleware(options)]
|
|
@@ -6466,16 +6676,16 @@ const getUserStats = (options) => createAuthEndpoint("/dash/user-stats", {
|
|
|
6466
6676
|
const twoMonthsAgo = /* @__PURE__ */ new Date(now.getTime() - 1440 * 60 * 60 * 1e3);
|
|
6467
6677
|
const activityTrackingEnabled = !!options.activityTracking?.enabled;
|
|
6468
6678
|
const storeInSecondaryStorageOnly = isSessionInSecondaryStorageOnly(ctx.context);
|
|
6469
|
-
const [
|
|
6470
|
-
ctx.context.adapter.count({
|
|
6679
|
+
const [rDailySignups, rPrevDaySignups, rWeeklySignups, rPrevWeekSignups, rMonthlySignups, rPrevMonthSignups, rTotalUsers, rActiveDaily, rActivePrevDay, rActiveWeekly, rActivePrevWeek, rActiveMonthly, rActivePrevMonth] = await withConcurrency([
|
|
6680
|
+
optionalQuery(ctx, "daily signups", () => ctx.context.adapter.count({
|
|
6471
6681
|
model: "user",
|
|
6472
6682
|
where: [{
|
|
6473
6683
|
field: "createdAt",
|
|
6474
6684
|
operator: "gte",
|
|
6475
6685
|
value: oneDayAgo
|
|
6476
6686
|
}]
|
|
6477
|
-
}),
|
|
6478
|
-
ctx.context.adapter.count({
|
|
6687
|
+
})),
|
|
6688
|
+
optionalQuery(ctx, "previous day signups", () => ctx.context.adapter.count({
|
|
6479
6689
|
model: "user",
|
|
6480
6690
|
where: [{
|
|
6481
6691
|
field: "createdAt",
|
|
@@ -6486,16 +6696,16 @@ const getUserStats = (options) => createAuthEndpoint("/dash/user-stats", {
|
|
|
6486
6696
|
operator: "lt",
|
|
6487
6697
|
value: oneDayAgo
|
|
6488
6698
|
}]
|
|
6489
|
-
}),
|
|
6490
|
-
ctx.context.adapter.count({
|
|
6699
|
+
})),
|
|
6700
|
+
optionalQuery(ctx, "weekly signups", () => ctx.context.adapter.count({
|
|
6491
6701
|
model: "user",
|
|
6492
6702
|
where: [{
|
|
6493
6703
|
field: "createdAt",
|
|
6494
6704
|
operator: "gte",
|
|
6495
6705
|
value: oneWeekAgo
|
|
6496
6706
|
}]
|
|
6497
|
-
}),
|
|
6498
|
-
ctx.context.adapter.count({
|
|
6707
|
+
})),
|
|
6708
|
+
optionalQuery(ctx, "previous week signups", () => ctx.context.adapter.count({
|
|
6499
6709
|
model: "user",
|
|
6500
6710
|
where: [{
|
|
6501
6711
|
field: "createdAt",
|
|
@@ -6506,16 +6716,16 @@ const getUserStats = (options) => createAuthEndpoint("/dash/user-stats", {
|
|
|
6506
6716
|
operator: "lt",
|
|
6507
6717
|
value: oneWeekAgo
|
|
6508
6718
|
}]
|
|
6509
|
-
}),
|
|
6510
|
-
ctx.context.adapter.count({
|
|
6719
|
+
})),
|
|
6720
|
+
optionalQuery(ctx, "monthly signups", () => ctx.context.adapter.count({
|
|
6511
6721
|
model: "user",
|
|
6512
6722
|
where: [{
|
|
6513
6723
|
field: "createdAt",
|
|
6514
6724
|
operator: "gte",
|
|
6515
6725
|
value: oneMonthAgo
|
|
6516
6726
|
}]
|
|
6517
|
-
}),
|
|
6518
|
-
ctx.context.adapter.count({
|
|
6727
|
+
})),
|
|
6728
|
+
optionalQuery(ctx, "previous month signups", () => ctx.context.adapter.count({
|
|
6519
6729
|
model: "user",
|
|
6520
6730
|
where: [{
|
|
6521
6731
|
field: "createdAt",
|
|
@@ -6526,75 +6736,74 @@ const getUserStats = (options) => createAuthEndpoint("/dash/user-stats", {
|
|
|
6526
6736
|
operator: "lt",
|
|
6527
6737
|
value: oneMonthAgo
|
|
6528
6738
|
}]
|
|
6529
|
-
}),
|
|
6530
|
-
ctx.context.adapter.count({ model: "user" }),
|
|
6531
|
-
countUniqueActiveUsers(ctx, { from: oneDayAgo }, {
|
|
6739
|
+
})),
|
|
6740
|
+
optionalQuery(ctx, "total users", () => ctx.context.adapter.count({ model: "user" })),
|
|
6741
|
+
optionalQuery(ctx, "active users (daily window)", () => countUniqueActiveUsers(ctx, { from: oneDayAgo }, {
|
|
6532
6742
|
storeInSecondaryStorageOnly,
|
|
6533
6743
|
activityTrackingEnabled
|
|
6534
|
-
}),
|
|
6535
|
-
countUniqueActiveUsers(ctx, {
|
|
6744
|
+
})),
|
|
6745
|
+
optionalQuery(ctx, "active users (previous day window)", () => countUniqueActiveUsers(ctx, {
|
|
6536
6746
|
from: twoDaysAgo,
|
|
6537
6747
|
to: oneDayAgo
|
|
6538
6748
|
}, {
|
|
6539
6749
|
storeInSecondaryStorageOnly,
|
|
6540
6750
|
activityTrackingEnabled
|
|
6541
|
-
}),
|
|
6542
|
-
countUniqueActiveUsers(ctx, { from: oneWeekAgo }, {
|
|
6751
|
+
})),
|
|
6752
|
+
optionalQuery(ctx, "active users (weekly window)", () => countUniqueActiveUsers(ctx, { from: oneWeekAgo }, {
|
|
6543
6753
|
storeInSecondaryStorageOnly,
|
|
6544
6754
|
activityTrackingEnabled
|
|
6545
|
-
}),
|
|
6546
|
-
countUniqueActiveUsers(ctx, {
|
|
6755
|
+
})),
|
|
6756
|
+
optionalQuery(ctx, "active users (previous week window)", () => countUniqueActiveUsers(ctx, {
|
|
6547
6757
|
from: twoWeeksAgo,
|
|
6548
6758
|
to: oneWeekAgo
|
|
6549
6759
|
}, {
|
|
6550
6760
|
storeInSecondaryStorageOnly,
|
|
6551
6761
|
activityTrackingEnabled
|
|
6552
|
-
}),
|
|
6553
|
-
countUniqueActiveUsers(ctx, { from: oneMonthAgo }, {
|
|
6762
|
+
})),
|
|
6763
|
+
optionalQuery(ctx, "active users (monthly window)", () => countUniqueActiveUsers(ctx, { from: oneMonthAgo }, {
|
|
6554
6764
|
storeInSecondaryStorageOnly,
|
|
6555
6765
|
activityTrackingEnabled
|
|
6556
|
-
}),
|
|
6557
|
-
countUniqueActiveUsers(ctx, {
|
|
6766
|
+
})),
|
|
6767
|
+
optionalQuery(ctx, "active users (previous month window)", () => countUniqueActiveUsers(ctx, {
|
|
6558
6768
|
from: twoMonthsAgo,
|
|
6559
6769
|
to: oneMonthAgo
|
|
6560
6770
|
}, {
|
|
6561
6771
|
storeInSecondaryStorageOnly,
|
|
6562
6772
|
activityTrackingEnabled
|
|
6563
|
-
})
|
|
6564
|
-
]);
|
|
6773
|
+
}))
|
|
6774
|
+
], (fn) => fn(), { concurrency: 5 });
|
|
6565
6775
|
const calculatePercentage = (current, previous) => {
|
|
6566
6776
|
if (previous === 0) return current > 0 ? 100 : 0;
|
|
6567
6777
|
return (current - previous) / previous * 100;
|
|
6568
6778
|
};
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6779
|
+
const degraded = [
|
|
6780
|
+
rDailySignups,
|
|
6781
|
+
rPrevDaySignups,
|
|
6782
|
+
rWeeklySignups,
|
|
6783
|
+
rPrevWeekSignups,
|
|
6784
|
+
rMonthlySignups,
|
|
6785
|
+
rPrevMonthSignups,
|
|
6786
|
+
rTotalUsers,
|
|
6787
|
+
rActiveDaily,
|
|
6788
|
+
rActivePrevDay,
|
|
6789
|
+
rActiveWeekly,
|
|
6790
|
+
rActivePrevWeek,
|
|
6791
|
+
rActiveMonthly,
|
|
6792
|
+
rActivePrevMonth
|
|
6793
|
+
].some((r) => !r.ok);
|
|
6794
|
+
const body = {
|
|
6795
|
+
daily: signUpPeriod(rDailySignups, rPrevDaySignups, calculatePercentage),
|
|
6796
|
+
weekly: signUpPeriod(rWeeklySignups, rPrevWeekSignups, calculatePercentage),
|
|
6797
|
+
monthly: signUpPeriod(rMonthlySignups, rPrevMonthSignups, calculatePercentage),
|
|
6798
|
+
total: rTotalUsers.ok ? rTotalUsers.value : null,
|
|
6583
6799
|
activeUsers: {
|
|
6584
|
-
daily:
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
},
|
|
6588
|
-
weekly: {
|
|
6589
|
-
active: weeklyActiveCount,
|
|
6590
|
-
percentage: calculatePercentage(weeklyActiveCount, previousWeeklyActiveCount)
|
|
6591
|
-
},
|
|
6592
|
-
monthly: {
|
|
6593
|
-
active: monthlyActiveCount,
|
|
6594
|
-
percentage: calculatePercentage(monthlyActiveCount, previousMonthlyActiveCount)
|
|
6595
|
-
}
|
|
6800
|
+
daily: activePeriod(rActiveDaily, rActivePrevDay, calculatePercentage),
|
|
6801
|
+
weekly: activePeriod(rActiveWeekly, rActivePrevWeek, calculatePercentage),
|
|
6802
|
+
monthly: activePeriod(rActiveMonthly, rActivePrevMonth, calculatePercentage)
|
|
6596
6803
|
}
|
|
6597
6804
|
};
|
|
6805
|
+
if (degraded) body.degraded = true;
|
|
6806
|
+
return body;
|
|
6598
6807
|
});
|
|
6599
6808
|
const getUserGraphData = (options) => createAuthEndpoint("/dash/user-graph-data", {
|
|
6600
6809
|
method: "GET",
|
|
@@ -7188,6 +7397,7 @@ const dash = (options) => {
|
|
|
7188
7397
|
return {
|
|
7189
7398
|
id: "dash",
|
|
7190
7399
|
options: opts,
|
|
7400
|
+
version: PLUGIN_VERSION,
|
|
7191
7401
|
init(ctx) {
|
|
7192
7402
|
const organizationPlugin = ctx.getPlugin("organization");
|
|
7193
7403
|
if (organizationPlugin) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { a as dashClient, i as DashGetAuditLogsInput, n as DashAuditLogsResponse, r as DashClientOptions, t as DashAuditLog } from "./dash-client-hJHp7l_X.mjs";
|
|
2
|
+
import { BetterAuthClientPlugin } from "better-auth";
|
|
3
|
+
|
|
4
|
+
//#region src/sentinel/native/client.d.ts
|
|
5
|
+
interface SentinelNativeClientOptions {
|
|
6
|
+
identifyUrl?: string;
|
|
7
|
+
autoSolveChallenge?: boolean;
|
|
8
|
+
onChallengeReceived?: (reason: string) => void;
|
|
9
|
+
onChallengeSolved?: (solveTimeMs: number) => void;
|
|
10
|
+
onChallengeFailed?: (error: Error) => void;
|
|
11
|
+
storage?: {
|
|
12
|
+
getItem: (key: string) => Promise<string | null>;
|
|
13
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
declare const sentinelNativeClient: (options?: SentinelNativeClientOptions) => BetterAuthClientPlugin;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { type DashAuditLog, type DashAuditLogsResponse, type DashClientOptions, type DashGetAuditLogsInput, type SentinelNativeClientOptions, dashClient, sentinelNativeClient };
|