@digilogiclabs/platform-core 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1513 -132
- package/dist/index.d.ts +1513 -132
- package/dist/index.js +1322 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1233 -24
- package/dist/index.mjs.map +1 -1
- package/dist/migrate.js +0 -0
- package/dist/security-headers.js.map +1 -1
- package/dist/security-headers.mjs.map +1 -1
- package/dist/testing.js +3 -1
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +9 -2
- package/dist/testing.mjs.map +1 -1
- package/package.json +11 -11
package/dist/index.mjs
CHANGED
|
@@ -1745,7 +1745,7 @@ var init_RedisCache = __esm({
|
|
|
1745
1745
|
* Create a RedisCache from configuration
|
|
1746
1746
|
*/
|
|
1747
1747
|
static async create(config) {
|
|
1748
|
-
const { default:
|
|
1748
|
+
const { default: Redis2 } = await import("ioredis");
|
|
1749
1749
|
let client;
|
|
1750
1750
|
if (config.cluster) {
|
|
1751
1751
|
const { Cluster } = await import("ioredis");
|
|
@@ -1758,7 +1758,7 @@ var init_RedisCache = __esm({
|
|
|
1758
1758
|
}
|
|
1759
1759
|
});
|
|
1760
1760
|
} else if (config.sentinel) {
|
|
1761
|
-
client = new
|
|
1761
|
+
client = new Redis2({
|
|
1762
1762
|
sentinels: config.sentinel.sentinels,
|
|
1763
1763
|
name: config.sentinel.name,
|
|
1764
1764
|
password: config.password,
|
|
@@ -1767,7 +1767,7 @@ var init_RedisCache = __esm({
|
|
|
1767
1767
|
});
|
|
1768
1768
|
} else {
|
|
1769
1769
|
const options = toIORedisOptions(config);
|
|
1770
|
-
client = typeof options === "string" ? new
|
|
1770
|
+
client = typeof options === "string" ? new Redis2(options) : new Redis2(options);
|
|
1771
1771
|
}
|
|
1772
1772
|
if (!config.lazyConnect) {
|
|
1773
1773
|
await new Promise((resolve, reject) => {
|
|
@@ -1901,9 +1901,9 @@ var init_RedisCache = __esm({
|
|
|
1901
1901
|
}
|
|
1902
1902
|
async subscribe(channel, callback) {
|
|
1903
1903
|
if (!this.subscriberClient) {
|
|
1904
|
-
const { default:
|
|
1904
|
+
const { default: Redis2 } = await import("ioredis");
|
|
1905
1905
|
const options = toIORedisOptions(this.config);
|
|
1906
|
-
this.subscriberClient = typeof options === "string" ? new
|
|
1906
|
+
this.subscriberClient = typeof options === "string" ? new Redis2(options) : new Redis2(options);
|
|
1907
1907
|
this.subscriberClient.on("message", (ch, message) => {
|
|
1908
1908
|
const callbacks = this.subscriptions.get(ch);
|
|
1909
1909
|
if (callbacks) {
|
|
@@ -6974,9 +6974,7 @@ var init_NodeCrypto = __esm({
|
|
|
6974
6974
|
);
|
|
6975
6975
|
}
|
|
6976
6976
|
this.masterKey = Buffer.from(config.masterKey, "hex");
|
|
6977
|
-
this.hmacKey = config.hmacKey ? Buffer.from(config.hmacKey, "hex") : Buffer.from(
|
|
6978
|
-
hkdfSync("sha256", this.masterKey, "", "hmac-key", 32)
|
|
6979
|
-
);
|
|
6977
|
+
this.hmacKey = config.hmacKey ? Buffer.from(config.hmacKey, "hex") : Buffer.from(hkdfSync("sha256", this.masterKey, "", "hmac-key", 32));
|
|
6980
6978
|
const keyId = this.generateKeyId();
|
|
6981
6979
|
const dek = this.deriveDEK(keyId);
|
|
6982
6980
|
this.keys.set(keyId, {
|
|
@@ -14095,7 +14093,9 @@ var RAGConfigSchema = z.object({
|
|
|
14095
14093
|
var CryptoConfigSchema = z.object({
|
|
14096
14094
|
enabled: z.boolean().default(false).describe("Enable field-level encryption"),
|
|
14097
14095
|
masterKey: z.string().optional().describe("256-bit master key as hex (64 chars). Required when enabled."),
|
|
14098
|
-
hmacKey: z.string().optional().describe(
|
|
14096
|
+
hmacKey: z.string().optional().describe(
|
|
14097
|
+
"HMAC key for deterministic hashing (derived from master key if not provided)"
|
|
14098
|
+
)
|
|
14099
14099
|
}).refine(
|
|
14100
14100
|
(data) => {
|
|
14101
14101
|
if (data.enabled) {
|
|
@@ -15416,7 +15416,12 @@ init_IAI();
|
|
|
15416
15416
|
init_IRAG();
|
|
15417
15417
|
|
|
15418
15418
|
// src/adapters/memory/MemoryCrypto.ts
|
|
15419
|
-
import {
|
|
15419
|
+
import {
|
|
15420
|
+
randomBytes as randomBytes21,
|
|
15421
|
+
createCipheriv,
|
|
15422
|
+
createDecipheriv,
|
|
15423
|
+
createHmac
|
|
15424
|
+
} from "crypto";
|
|
15420
15425
|
var MemoryCrypto = class {
|
|
15421
15426
|
keys = /* @__PURE__ */ new Map();
|
|
15422
15427
|
activeKeyId;
|
|
@@ -15610,9 +15615,9 @@ async function createCacheAdapter(config) {
|
|
|
15610
15615
|
"Upstash requires UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN environment variables"
|
|
15611
15616
|
);
|
|
15612
15617
|
}
|
|
15613
|
-
const { Redis } = await import("@upstash/redis");
|
|
15618
|
+
const { Redis: Redis2 } = await import("@upstash/redis");
|
|
15614
15619
|
const { UpstashCache: UpstashCache2 } = await Promise.resolve().then(() => (init_UpstashCache(), UpstashCache_exports));
|
|
15615
|
-
const client = new
|
|
15620
|
+
const client = new Redis2({
|
|
15616
15621
|
url: config.cache.upstashUrl,
|
|
15617
15622
|
token: config.cache.upstashToken
|
|
15618
15623
|
});
|
|
@@ -15911,15 +15916,17 @@ function validateTlsSecurity(config) {
|
|
|
15911
15916
|
async function createPlatformAsync(config) {
|
|
15912
15917
|
const finalConfig = config ? deepMerge(loadConfig(), config) : loadConfig();
|
|
15913
15918
|
validateTlsSecurity(finalConfig);
|
|
15914
|
-
const [db, cache, storage, email, queue, tracing, crypto2] = await Promise.all(
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
|
|
15922
|
-
|
|
15919
|
+
const [db, cache, storage, email, queue, tracing, crypto2] = await Promise.all(
|
|
15920
|
+
[
|
|
15921
|
+
createDatabaseAdapter(finalConfig),
|
|
15922
|
+
createCacheAdapter(finalConfig),
|
|
15923
|
+
createStorageAdapter(finalConfig),
|
|
15924
|
+
createEmailAdapter(finalConfig),
|
|
15925
|
+
createQueueAdapter(finalConfig),
|
|
15926
|
+
createTracingAdapter(finalConfig),
|
|
15927
|
+
createCryptoAdapter(finalConfig)
|
|
15928
|
+
]
|
|
15929
|
+
);
|
|
15923
15930
|
const logger = createLogger(finalConfig);
|
|
15924
15931
|
const metrics = createMetrics(finalConfig);
|
|
15925
15932
|
const ai = await createAIAdapter(finalConfig);
|
|
@@ -17499,6 +17506,7 @@ var FallbackStrategies = {
|
|
|
17499
17506
|
};
|
|
17500
17507
|
|
|
17501
17508
|
// src/security.ts
|
|
17509
|
+
import { timingSafeEqual } from "crypto";
|
|
17502
17510
|
var URL_PROTOCOL_PATTERN = /(https?:\/\/|ftp:\/\/|www\.)\S+/i;
|
|
17503
17511
|
var URL_DOMAIN_PATTERN = /\b[\w.-]+\.(com|net|org|io|co|dev|app|xyz|info|biz|me|us|uk|edu|gov)\b/i;
|
|
17504
17512
|
var HTML_TAG_PATTERN = /<[^>]*>/;
|
|
@@ -17523,6 +17531,37 @@ function defangUrl(str) {
|
|
|
17523
17531
|
function sanitizeForEmail(str) {
|
|
17524
17532
|
return escapeHtml(str);
|
|
17525
17533
|
}
|
|
17534
|
+
function constantTimeEqual(a, b) {
|
|
17535
|
+
try {
|
|
17536
|
+
const aBuf = Buffer.from(a, "utf-8");
|
|
17537
|
+
const bBuf = Buffer.from(b, "utf-8");
|
|
17538
|
+
if (aBuf.length !== bBuf.length) return false;
|
|
17539
|
+
return timingSafeEqual(aBuf, bBuf);
|
|
17540
|
+
} catch {
|
|
17541
|
+
return false;
|
|
17542
|
+
}
|
|
17543
|
+
}
|
|
17544
|
+
function sanitizeApiError(error, statusCode, isDevelopment = false) {
|
|
17545
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
17546
|
+
const message = error instanceof Error ? error.message : String(error || "Bad request");
|
|
17547
|
+
return { message };
|
|
17548
|
+
}
|
|
17549
|
+
const result = {
|
|
17550
|
+
message: "An internal error occurred. Please try again later.",
|
|
17551
|
+
code: "INTERNAL_ERROR"
|
|
17552
|
+
};
|
|
17553
|
+
if (isDevelopment && error instanceof Error) {
|
|
17554
|
+
result.stack = error.stack;
|
|
17555
|
+
}
|
|
17556
|
+
return result;
|
|
17557
|
+
}
|
|
17558
|
+
function getCorrelationId2(headers) {
|
|
17559
|
+
const get = typeof headers === "function" ? headers : (name) => {
|
|
17560
|
+
const val = headers[name] ?? headers[name.toLowerCase()];
|
|
17561
|
+
return Array.isArray(val) ? val[0] : val;
|
|
17562
|
+
};
|
|
17563
|
+
return get("x-request-id") || get("X-Request-ID") || get("x-correlation-id") || get("X-Correlation-ID") || crypto.randomUUID();
|
|
17564
|
+
}
|
|
17526
17565
|
|
|
17527
17566
|
// src/security-headers.ts
|
|
17528
17567
|
var SecurityHeaderPresets = {
|
|
@@ -17756,6 +17795,850 @@ function buildPagination(page, limit, total) {
|
|
|
17756
17795
|
};
|
|
17757
17796
|
}
|
|
17758
17797
|
|
|
17798
|
+
// src/auth/keycloak.ts
|
|
17799
|
+
var KEYCLOAK_DEFAULT_ROLES = [
|
|
17800
|
+
"offline_access",
|
|
17801
|
+
"uma_authorization"
|
|
17802
|
+
];
|
|
17803
|
+
function parseKeycloakRoles(accessToken, additionalDefaultRoles = []) {
|
|
17804
|
+
if (!accessToken) return [];
|
|
17805
|
+
try {
|
|
17806
|
+
const parts = accessToken.split(".");
|
|
17807
|
+
if (parts.length !== 3) return [];
|
|
17808
|
+
const payload = parts[1];
|
|
17809
|
+
const decoded = JSON.parse(atob(payload));
|
|
17810
|
+
const realmRoles = decoded.realm_roles ?? decoded.realm_access?.roles;
|
|
17811
|
+
if (!Array.isArray(realmRoles)) return [];
|
|
17812
|
+
const filterSet = /* @__PURE__ */ new Set([
|
|
17813
|
+
...KEYCLOAK_DEFAULT_ROLES,
|
|
17814
|
+
...additionalDefaultRoles
|
|
17815
|
+
]);
|
|
17816
|
+
return realmRoles.filter(
|
|
17817
|
+
(role) => typeof role === "string" && !filterSet.has(role)
|
|
17818
|
+
);
|
|
17819
|
+
} catch {
|
|
17820
|
+
return [];
|
|
17821
|
+
}
|
|
17822
|
+
}
|
|
17823
|
+
function hasRole(roles, role) {
|
|
17824
|
+
return roles?.includes(role) ?? false;
|
|
17825
|
+
}
|
|
17826
|
+
function hasAnyRole(roles, requiredRoles) {
|
|
17827
|
+
if (!roles || roles.length === 0) return false;
|
|
17828
|
+
return requiredRoles.some((role) => roles.includes(role));
|
|
17829
|
+
}
|
|
17830
|
+
function hasAllRoles(roles, requiredRoles) {
|
|
17831
|
+
if (!roles || roles.length === 0) return false;
|
|
17832
|
+
return requiredRoles.every((role) => roles.includes(role));
|
|
17833
|
+
}
|
|
17834
|
+
function isTokenExpired(expiresAt, bufferMs = 6e4) {
|
|
17835
|
+
if (!expiresAt) return true;
|
|
17836
|
+
return Date.now() >= expiresAt - bufferMs;
|
|
17837
|
+
}
|
|
17838
|
+
function buildTokenRefreshParams(config, refreshToken) {
|
|
17839
|
+
return new URLSearchParams({
|
|
17840
|
+
grant_type: "refresh_token",
|
|
17841
|
+
client_id: config.clientId,
|
|
17842
|
+
client_secret: config.clientSecret,
|
|
17843
|
+
refresh_token: refreshToken
|
|
17844
|
+
});
|
|
17845
|
+
}
|
|
17846
|
+
function getTokenEndpoint(issuer) {
|
|
17847
|
+
const base = issuer.endsWith("/") ? issuer.slice(0, -1) : issuer;
|
|
17848
|
+
return `${base}/protocol/openid-connect/token`;
|
|
17849
|
+
}
|
|
17850
|
+
function getEndSessionEndpoint(issuer) {
|
|
17851
|
+
const base = issuer.endsWith("/") ? issuer.slice(0, -1) : issuer;
|
|
17852
|
+
return `${base}/protocol/openid-connect/logout`;
|
|
17853
|
+
}
|
|
17854
|
+
async function refreshKeycloakToken(config, refreshToken, additionalDefaultRoles) {
|
|
17855
|
+
try {
|
|
17856
|
+
const endpoint = getTokenEndpoint(config.issuer);
|
|
17857
|
+
const params = buildTokenRefreshParams(config, refreshToken);
|
|
17858
|
+
const response = await fetch(endpoint, {
|
|
17859
|
+
method: "POST",
|
|
17860
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
17861
|
+
body: params
|
|
17862
|
+
});
|
|
17863
|
+
if (!response.ok) {
|
|
17864
|
+
const body = await response.text().catch(() => "");
|
|
17865
|
+
return {
|
|
17866
|
+
ok: false,
|
|
17867
|
+
error: `Token refresh failed: HTTP ${response.status} - ${body}`
|
|
17868
|
+
};
|
|
17869
|
+
}
|
|
17870
|
+
const data = await response.json();
|
|
17871
|
+
return {
|
|
17872
|
+
ok: true,
|
|
17873
|
+
tokens: {
|
|
17874
|
+
accessToken: data.access_token,
|
|
17875
|
+
refreshToken: data.refresh_token ?? refreshToken,
|
|
17876
|
+
idToken: data.id_token,
|
|
17877
|
+
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
17878
|
+
roles: parseKeycloakRoles(data.access_token, additionalDefaultRoles)
|
|
17879
|
+
}
|
|
17880
|
+
};
|
|
17881
|
+
} catch (error) {
|
|
17882
|
+
return {
|
|
17883
|
+
ok: false,
|
|
17884
|
+
error: error instanceof Error ? error.message : "Token refresh failed"
|
|
17885
|
+
};
|
|
17886
|
+
}
|
|
17887
|
+
}
|
|
17888
|
+
|
|
17889
|
+
// src/auth/nextjs-keycloak.ts
|
|
17890
|
+
function buildAuthCookies(config = {}) {
|
|
17891
|
+
const { domain, sessionToken = true, callbackUrl = true } = config;
|
|
17892
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
17893
|
+
const cookieDomain = isProduction ? domain : void 0;
|
|
17894
|
+
const baseOptions = {
|
|
17895
|
+
httpOnly: true,
|
|
17896
|
+
sameSite: "lax",
|
|
17897
|
+
path: "/",
|
|
17898
|
+
secure: isProduction,
|
|
17899
|
+
domain: cookieDomain
|
|
17900
|
+
};
|
|
17901
|
+
const cookies = {
|
|
17902
|
+
pkceCodeVerifier: {
|
|
17903
|
+
name: "authjs.pkce.code_verifier",
|
|
17904
|
+
options: { ...baseOptions }
|
|
17905
|
+
},
|
|
17906
|
+
state: {
|
|
17907
|
+
name: "authjs.state",
|
|
17908
|
+
options: { ...baseOptions }
|
|
17909
|
+
}
|
|
17910
|
+
};
|
|
17911
|
+
if (sessionToken) {
|
|
17912
|
+
cookies.sessionToken = {
|
|
17913
|
+
name: isProduction ? "__Secure-authjs.session-token" : "authjs.session-token",
|
|
17914
|
+
options: { ...baseOptions }
|
|
17915
|
+
};
|
|
17916
|
+
}
|
|
17917
|
+
if (callbackUrl) {
|
|
17918
|
+
cookies.callbackUrl = {
|
|
17919
|
+
name: isProduction ? "__Secure-authjs.callback-url" : "authjs.callback-url",
|
|
17920
|
+
options: { ...baseOptions }
|
|
17921
|
+
};
|
|
17922
|
+
}
|
|
17923
|
+
return cookies;
|
|
17924
|
+
}
|
|
17925
|
+
function buildRedirectCallback(config = {}) {
|
|
17926
|
+
const { allowWwwVariant = false } = config;
|
|
17927
|
+
return async ({ url, baseUrl }) => {
|
|
17928
|
+
if (url.startsWith("/")) return `${baseUrl}${url}`;
|
|
17929
|
+
try {
|
|
17930
|
+
if (new URL(url).origin === baseUrl) return url;
|
|
17931
|
+
} catch {
|
|
17932
|
+
return baseUrl;
|
|
17933
|
+
}
|
|
17934
|
+
if (allowWwwVariant) {
|
|
17935
|
+
try {
|
|
17936
|
+
const urlHost = new URL(url).hostname;
|
|
17937
|
+
const baseHost = new URL(baseUrl).hostname;
|
|
17938
|
+
if (urlHost === `www.${baseHost}` || baseHost === `www.${urlHost}`) {
|
|
17939
|
+
return url;
|
|
17940
|
+
}
|
|
17941
|
+
} catch {
|
|
17942
|
+
}
|
|
17943
|
+
}
|
|
17944
|
+
return baseUrl;
|
|
17945
|
+
};
|
|
17946
|
+
}
|
|
17947
|
+
function buildKeycloakCallbacks(config) {
|
|
17948
|
+
const {
|
|
17949
|
+
issuer,
|
|
17950
|
+
clientId,
|
|
17951
|
+
clientSecret,
|
|
17952
|
+
defaultRoles = [],
|
|
17953
|
+
debug = process.env.NODE_ENV === "development"
|
|
17954
|
+
} = config;
|
|
17955
|
+
const kcConfig = { issuer, clientId, clientSecret };
|
|
17956
|
+
function log(message, meta) {
|
|
17957
|
+
if (debug) {
|
|
17958
|
+
console.log(`[Auth] ${message}`, meta ? JSON.stringify(meta) : "");
|
|
17959
|
+
}
|
|
17960
|
+
}
|
|
17961
|
+
return {
|
|
17962
|
+
/**
|
|
17963
|
+
* JWT callback — stores Keycloak tokens and handles refresh.
|
|
17964
|
+
*
|
|
17965
|
+
* Compatible with Auth.js v5 JWT callback signature.
|
|
17966
|
+
*/
|
|
17967
|
+
async jwt({
|
|
17968
|
+
token,
|
|
17969
|
+
user,
|
|
17970
|
+
account
|
|
17971
|
+
}) {
|
|
17972
|
+
if (user) {
|
|
17973
|
+
token.id = token.sub ?? user.id;
|
|
17974
|
+
}
|
|
17975
|
+
if (account?.provider === "keycloak") {
|
|
17976
|
+
token.accessToken = account.access_token;
|
|
17977
|
+
token.refreshToken = account.refresh_token;
|
|
17978
|
+
token.idToken = account.id_token;
|
|
17979
|
+
token.roles = parseKeycloakRoles(
|
|
17980
|
+
account.access_token,
|
|
17981
|
+
defaultRoles
|
|
17982
|
+
);
|
|
17983
|
+
token.accessTokenExpires = account.expires_at ? account.expires_at * 1e3 : Date.now() + 3e5;
|
|
17984
|
+
return token;
|
|
17985
|
+
}
|
|
17986
|
+
if (!isTokenExpired(token.accessTokenExpires)) {
|
|
17987
|
+
return token;
|
|
17988
|
+
}
|
|
17989
|
+
if (token.refreshToken) {
|
|
17990
|
+
log("Token expired, attempting refresh...");
|
|
17991
|
+
const result = await refreshKeycloakToken(
|
|
17992
|
+
kcConfig,
|
|
17993
|
+
token.refreshToken,
|
|
17994
|
+
defaultRoles
|
|
17995
|
+
);
|
|
17996
|
+
if (result.ok) {
|
|
17997
|
+
token.accessToken = result.tokens.accessToken;
|
|
17998
|
+
token.idToken = result.tokens.idToken ?? token.idToken;
|
|
17999
|
+
token.refreshToken = result.tokens.refreshToken ?? token.refreshToken;
|
|
18000
|
+
token.accessTokenExpires = result.tokens.expiresAt;
|
|
18001
|
+
token.roles = result.tokens.roles;
|
|
18002
|
+
delete token.error;
|
|
18003
|
+
log("Token refreshed OK");
|
|
18004
|
+
return token;
|
|
18005
|
+
}
|
|
18006
|
+
log("Token refresh failed", { error: result.error });
|
|
18007
|
+
return { ...token, error: "RefreshTokenError" };
|
|
18008
|
+
}
|
|
18009
|
+
log("Token expired but no refresh token available");
|
|
18010
|
+
return { ...token, error: "RefreshTokenError" };
|
|
18011
|
+
},
|
|
18012
|
+
/**
|
|
18013
|
+
* Session callback — maps JWT fields to the session object.
|
|
18014
|
+
*
|
|
18015
|
+
* Compatible with Auth.js v5 session callback signature.
|
|
18016
|
+
*/
|
|
18017
|
+
async session({
|
|
18018
|
+
session,
|
|
18019
|
+
token
|
|
18020
|
+
}) {
|
|
18021
|
+
const user = session.user;
|
|
18022
|
+
if (user) {
|
|
18023
|
+
user.id = token.id || token.sub;
|
|
18024
|
+
user.roles = token.roles || [];
|
|
18025
|
+
}
|
|
18026
|
+
session.idToken = token.idToken;
|
|
18027
|
+
session.accessToken = token.accessToken;
|
|
18028
|
+
if (token.error) {
|
|
18029
|
+
session.error = token.error;
|
|
18030
|
+
}
|
|
18031
|
+
return session;
|
|
18032
|
+
}
|
|
18033
|
+
};
|
|
18034
|
+
}
|
|
18035
|
+
|
|
18036
|
+
// src/auth/api-security.ts
|
|
18037
|
+
var StandardRateLimitPresets = {
|
|
18038
|
+
/** General API: 100/min, 200/min authenticated */
|
|
18039
|
+
apiGeneral: {
|
|
18040
|
+
limit: 100,
|
|
18041
|
+
windowSeconds: 60,
|
|
18042
|
+
authenticatedLimit: 200
|
|
18043
|
+
},
|
|
18044
|
+
/** Admin operations: 100/min (admins are trusted) */
|
|
18045
|
+
adminAction: {
|
|
18046
|
+
limit: 100,
|
|
18047
|
+
windowSeconds: 60
|
|
18048
|
+
},
|
|
18049
|
+
/** AI/expensive operations: 20/hour, 50/hour authenticated */
|
|
18050
|
+
aiRequest: {
|
|
18051
|
+
limit: 20,
|
|
18052
|
+
windowSeconds: 3600,
|
|
18053
|
+
authenticatedLimit: 50
|
|
18054
|
+
},
|
|
18055
|
+
/** Auth attempts: 5/15min with 15min block */
|
|
18056
|
+
authAttempt: {
|
|
18057
|
+
limit: 5,
|
|
18058
|
+
windowSeconds: 900,
|
|
18059
|
+
blockDurationSeconds: 900
|
|
18060
|
+
},
|
|
18061
|
+
/** Contact/public forms: 10/hour */
|
|
18062
|
+
publicForm: {
|
|
18063
|
+
limit: 10,
|
|
18064
|
+
windowSeconds: 3600,
|
|
18065
|
+
blockDurationSeconds: 1800
|
|
18066
|
+
},
|
|
18067
|
+
/** Checkout/billing: 10/hour with 1hr block */
|
|
18068
|
+
checkout: {
|
|
18069
|
+
limit: 10,
|
|
18070
|
+
windowSeconds: 3600,
|
|
18071
|
+
blockDurationSeconds: 3600
|
|
18072
|
+
}
|
|
18073
|
+
};
|
|
18074
|
+
function resolveRateLimitIdentifier(session, clientIp) {
|
|
18075
|
+
if (session?.user?.id) {
|
|
18076
|
+
return { identifier: `user:${session.user.id}`, isAuthenticated: true };
|
|
18077
|
+
}
|
|
18078
|
+
if (session?.user?.email) {
|
|
18079
|
+
return {
|
|
18080
|
+
identifier: `email:${session.user.email}`,
|
|
18081
|
+
isAuthenticated: true
|
|
18082
|
+
};
|
|
18083
|
+
}
|
|
18084
|
+
return { identifier: `ip:${clientIp}`, isAuthenticated: false };
|
|
18085
|
+
}
|
|
18086
|
+
function extractClientIp(getHeader) {
|
|
18087
|
+
return getHeader("cf-connecting-ip") || getHeader("x-real-ip") || getHeader("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
|
|
18088
|
+
}
|
|
18089
|
+
function buildRateLimitHeaders(limit, remaining, resetAtMs) {
|
|
18090
|
+
return {
|
|
18091
|
+
"X-RateLimit-Limit": String(limit),
|
|
18092
|
+
"X-RateLimit-Remaining": String(Math.max(0, remaining)),
|
|
18093
|
+
"X-RateLimit-Reset": String(Math.ceil(resetAtMs / 1e3))
|
|
18094
|
+
};
|
|
18095
|
+
}
|
|
18096
|
+
function buildErrorBody(error, extra) {
|
|
18097
|
+
return { error, ...extra };
|
|
18098
|
+
}
|
|
18099
|
+
var WrapperPresets = {
|
|
18100
|
+
/** Public route: no auth, rate limited */
|
|
18101
|
+
public: {
|
|
18102
|
+
requireAuth: false,
|
|
18103
|
+
requireAdmin: false,
|
|
18104
|
+
rateLimit: "apiGeneral"
|
|
18105
|
+
},
|
|
18106
|
+
/** Authenticated route: requires session */
|
|
18107
|
+
authenticated: {
|
|
18108
|
+
requireAuth: true,
|
|
18109
|
+
requireAdmin: false,
|
|
18110
|
+
rateLimit: "apiGeneral"
|
|
18111
|
+
},
|
|
18112
|
+
/** Admin route: requires session with admin role */
|
|
18113
|
+
admin: {
|
|
18114
|
+
requireAuth: true,
|
|
18115
|
+
requireAdmin: true,
|
|
18116
|
+
rateLimit: "adminAction"
|
|
18117
|
+
},
|
|
18118
|
+
/** Legacy admin: accepts session OR bearer token */
|
|
18119
|
+
legacyAdmin: {
|
|
18120
|
+
requireAuth: true,
|
|
18121
|
+
requireAdmin: true,
|
|
18122
|
+
allowBearerToken: true,
|
|
18123
|
+
rateLimit: "adminAction"
|
|
18124
|
+
},
|
|
18125
|
+
/** AI/expensive: requires auth, strict rate limit */
|
|
18126
|
+
ai: {
|
|
18127
|
+
requireAuth: true,
|
|
18128
|
+
requireAdmin: false,
|
|
18129
|
+
rateLimit: "aiRequest"
|
|
18130
|
+
},
|
|
18131
|
+
/** Cron: no rate limit, admin-level access */
|
|
18132
|
+
cron: {
|
|
18133
|
+
requireAuth: true,
|
|
18134
|
+
requireAdmin: false,
|
|
18135
|
+
skipRateLimit: true,
|
|
18136
|
+
skipAudit: false
|
|
18137
|
+
}
|
|
18138
|
+
};
|
|
18139
|
+
|
|
18140
|
+
// src/auth/schemas.ts
|
|
18141
|
+
import { z as z2 } from "zod";
|
|
18142
|
+
var EmailSchema = z2.string().trim().toLowerCase().email("Invalid email address");
|
|
18143
|
+
var PasswordSchema = z2.string().min(8, "Password must be at least 8 characters").max(100, "Password must be less than 100 characters");
|
|
18144
|
+
var SlugSchema = z2.string().min(1, "Slug is required").max(100, "Slug must be less than 100 characters").regex(
|
|
18145
|
+
/^[a-z0-9-]+$/,
|
|
18146
|
+
"Slug can only contain lowercase letters, numbers, and hyphens"
|
|
18147
|
+
);
|
|
18148
|
+
var PhoneSchema = z2.string().regex(/^[\d\s()+.\-]{7,20}$/, "Invalid phone number format");
|
|
18149
|
+
var PersonNameSchema = z2.string().min(2, "Name must be at least 2 characters").max(100, "Name must be less than 100 characters").regex(
|
|
18150
|
+
/^[a-zA-Z\s\-']+$/,
|
|
18151
|
+
"Name can only contain letters, spaces, hyphens and apostrophes"
|
|
18152
|
+
);
|
|
18153
|
+
function createSafeTextSchema(options) {
|
|
18154
|
+
const {
|
|
18155
|
+
min,
|
|
18156
|
+
max,
|
|
18157
|
+
allowHtml = false,
|
|
18158
|
+
allowUrls = false,
|
|
18159
|
+
fieldName = "Text"
|
|
18160
|
+
} = options ?? {};
|
|
18161
|
+
let schema = z2.string();
|
|
18162
|
+
if (min !== void 0)
|
|
18163
|
+
schema = schema.min(min, `${fieldName} must be at least ${min} characters`);
|
|
18164
|
+
if (max !== void 0)
|
|
18165
|
+
schema = schema.max(
|
|
18166
|
+
max,
|
|
18167
|
+
`${fieldName} must be less than ${max} characters`
|
|
18168
|
+
);
|
|
18169
|
+
if (!allowHtml && !allowUrls) {
|
|
18170
|
+
return schema.refine((val) => !HTML_TAG_PATTERN.test(val), "HTML tags are not allowed").refine(
|
|
18171
|
+
(val) => !URL_PROTOCOL_PATTERN.test(val),
|
|
18172
|
+
"Links are not allowed for security reasons"
|
|
18173
|
+
).refine(
|
|
18174
|
+
(val) => !URL_DOMAIN_PATTERN.test(val),
|
|
18175
|
+
"Links are not allowed for security reasons"
|
|
18176
|
+
);
|
|
18177
|
+
}
|
|
18178
|
+
if (!allowHtml) {
|
|
18179
|
+
return schema.refine(
|
|
18180
|
+
(val) => !HTML_TAG_PATTERN.test(val),
|
|
18181
|
+
"HTML tags are not allowed"
|
|
18182
|
+
);
|
|
18183
|
+
}
|
|
18184
|
+
if (!allowUrls) {
|
|
18185
|
+
return schema.refine(
|
|
18186
|
+
(val) => !URL_PROTOCOL_PATTERN.test(val),
|
|
18187
|
+
"Links are not allowed for security reasons"
|
|
18188
|
+
).refine(
|
|
18189
|
+
(val) => !URL_DOMAIN_PATTERN.test(val),
|
|
18190
|
+
"Links are not allowed for security reasons"
|
|
18191
|
+
);
|
|
18192
|
+
}
|
|
18193
|
+
return schema;
|
|
18194
|
+
}
|
|
18195
|
+
var PaginationSchema = z2.object({
|
|
18196
|
+
page: z2.coerce.number().int().positive().default(1),
|
|
18197
|
+
limit: z2.coerce.number().int().positive().max(100).default(20),
|
|
18198
|
+
sortBy: z2.string().optional(),
|
|
18199
|
+
sortOrder: z2.enum(["asc", "desc"]).default("desc")
|
|
18200
|
+
});
|
|
18201
|
+
var DateRangeSchema = z2.object({
|
|
18202
|
+
startDate: z2.string().datetime(),
|
|
18203
|
+
endDate: z2.string().datetime()
|
|
18204
|
+
}).refine((data) => new Date(data.startDate) <= new Date(data.endDate), {
|
|
18205
|
+
message: "Start date must be before end date"
|
|
18206
|
+
});
|
|
18207
|
+
var SearchQuerySchema = z2.object({
|
|
18208
|
+
query: z2.string().min(1).max(200).trim(),
|
|
18209
|
+
page: z2.coerce.number().int().positive().default(1),
|
|
18210
|
+
limit: z2.coerce.number().int().positive().max(50).default(10)
|
|
18211
|
+
});
|
|
18212
|
+
var LoginSchema = z2.object({
|
|
18213
|
+
email: EmailSchema,
|
|
18214
|
+
password: PasswordSchema
|
|
18215
|
+
});
|
|
18216
|
+
var SignupSchema = z2.object({
|
|
18217
|
+
email: EmailSchema,
|
|
18218
|
+
password: PasswordSchema,
|
|
18219
|
+
name: z2.string().min(2).max(100).optional()
|
|
18220
|
+
});
|
|
18221
|
+
|
|
18222
|
+
// src/auth/feature-flags.ts
|
|
18223
|
+
function detectStage() {
|
|
18224
|
+
const stage = process.env.DEPLOYMENT_STAGE;
|
|
18225
|
+
if (stage === "staging" || stage === "preview") return stage;
|
|
18226
|
+
if (process.env.NODE_ENV === "production") return "production";
|
|
18227
|
+
return "development";
|
|
18228
|
+
}
|
|
18229
|
+
function resolveFlagValue(value) {
|
|
18230
|
+
if (typeof value === "boolean") return value;
|
|
18231
|
+
const envValue = process.env[value.envVar];
|
|
18232
|
+
if (envValue === void 0 || envValue === "") {
|
|
18233
|
+
return value.default ?? false;
|
|
18234
|
+
}
|
|
18235
|
+
return envValue === "true" || envValue === "1";
|
|
18236
|
+
}
|
|
18237
|
+
function createFeatureFlags(definitions) {
|
|
18238
|
+
return {
|
|
18239
|
+
/**
|
|
18240
|
+
* Resolve all flags for the current environment.
|
|
18241
|
+
* Call this once at startup or per-request for dynamic flags.
|
|
18242
|
+
*/
|
|
18243
|
+
resolve(stage) {
|
|
18244
|
+
const currentStage = stage ?? detectStage();
|
|
18245
|
+
const resolved = {};
|
|
18246
|
+
for (const [key, def] of Object.entries(definitions)) {
|
|
18247
|
+
const stageKey = currentStage === "preview" ? "staging" : currentStage;
|
|
18248
|
+
resolved[key] = resolveFlagValue(def[stageKey]);
|
|
18249
|
+
}
|
|
18250
|
+
return resolved;
|
|
18251
|
+
},
|
|
18252
|
+
/**
|
|
18253
|
+
* Check if a single flag is enabled.
|
|
18254
|
+
*/
|
|
18255
|
+
isEnabled(flag, stage) {
|
|
18256
|
+
const currentStage = stage ?? detectStage();
|
|
18257
|
+
const def = definitions[flag];
|
|
18258
|
+
const stageKey = currentStage === "preview" ? "staging" : currentStage;
|
|
18259
|
+
return resolveFlagValue(def[stageKey]);
|
|
18260
|
+
},
|
|
18261
|
+
/**
|
|
18262
|
+
* Get the flag definitions (for introspection/admin UI).
|
|
18263
|
+
*/
|
|
18264
|
+
definitions
|
|
18265
|
+
};
|
|
18266
|
+
}
|
|
18267
|
+
function buildAllowlist(config) {
|
|
18268
|
+
const fromEnv = config.envVar ? process.env[config.envVar]?.split(",").map((e) => e.trim().toLowerCase()).filter(Boolean) ?? [] : [];
|
|
18269
|
+
const fallback = config.fallback?.map((e) => e.toLowerCase()) ?? [];
|
|
18270
|
+
return [.../* @__PURE__ */ new Set([...fromEnv, ...fallback])];
|
|
18271
|
+
}
|
|
18272
|
+
function isAllowlisted(email, allowlist) {
|
|
18273
|
+
return allowlist.includes(email.toLowerCase());
|
|
18274
|
+
}
|
|
18275
|
+
|
|
18276
|
+
// src/auth/rate-limiter.ts
|
|
18277
|
+
var CommonRateLimits = {
|
|
18278
|
+
/** General API: 100/min, 200/min authenticated */
|
|
18279
|
+
apiGeneral: {
|
|
18280
|
+
limit: 100,
|
|
18281
|
+
windowSeconds: 60,
|
|
18282
|
+
authenticatedLimit: 200
|
|
18283
|
+
},
|
|
18284
|
+
/** Admin actions: 100/min */
|
|
18285
|
+
adminAction: {
|
|
18286
|
+
limit: 100,
|
|
18287
|
+
windowSeconds: 60
|
|
18288
|
+
},
|
|
18289
|
+
/** Auth attempts: 10/15min with 30min block */
|
|
18290
|
+
authAttempt: {
|
|
18291
|
+
limit: 10,
|
|
18292
|
+
windowSeconds: 900,
|
|
18293
|
+
blockDurationSeconds: 1800
|
|
18294
|
+
},
|
|
18295
|
+
/** AI/expensive requests: 20/hour, 50/hour authenticated */
|
|
18296
|
+
aiRequest: {
|
|
18297
|
+
limit: 20,
|
|
18298
|
+
windowSeconds: 3600,
|
|
18299
|
+
authenticatedLimit: 50
|
|
18300
|
+
},
|
|
18301
|
+
/** Public form submissions: 5/hour with 1hr block */
|
|
18302
|
+
publicForm: {
|
|
18303
|
+
limit: 5,
|
|
18304
|
+
windowSeconds: 3600,
|
|
18305
|
+
blockDurationSeconds: 3600
|
|
18306
|
+
},
|
|
18307
|
+
/** Checkout/billing: 10/hour with 1hr block */
|
|
18308
|
+
checkout: {
|
|
18309
|
+
limit: 10,
|
|
18310
|
+
windowSeconds: 3600,
|
|
18311
|
+
blockDurationSeconds: 3600
|
|
18312
|
+
}
|
|
18313
|
+
};
|
|
18314
|
+
function createMemoryRateLimitStore() {
|
|
18315
|
+
const windows = /* @__PURE__ */ new Map();
|
|
18316
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
18317
|
+
const cleanupInterval = setInterval(() => {
|
|
18318
|
+
const now = Date.now();
|
|
18319
|
+
for (const [key, entry] of windows) {
|
|
18320
|
+
if (entry.expiresAt < now) windows.delete(key);
|
|
18321
|
+
}
|
|
18322
|
+
for (const [key, expiry] of blocks) {
|
|
18323
|
+
if (expiry < now) blocks.delete(key);
|
|
18324
|
+
}
|
|
18325
|
+
}, 60 * 1e3);
|
|
18326
|
+
if (cleanupInterval.unref) {
|
|
18327
|
+
cleanupInterval.unref();
|
|
18328
|
+
}
|
|
18329
|
+
return {
|
|
18330
|
+
async increment(key, windowMs, now) {
|
|
18331
|
+
const windowStart = now - windowMs;
|
|
18332
|
+
let entry = windows.get(key);
|
|
18333
|
+
if (!entry) {
|
|
18334
|
+
entry = { timestamps: [], expiresAt: now + windowMs + 6e4 };
|
|
18335
|
+
windows.set(key, entry);
|
|
18336
|
+
}
|
|
18337
|
+
entry.timestamps = entry.timestamps.filter((t) => t > windowStart);
|
|
18338
|
+
entry.timestamps.push(now);
|
|
18339
|
+
entry.expiresAt = now + windowMs + 6e4;
|
|
18340
|
+
return { count: entry.timestamps.length };
|
|
18341
|
+
},
|
|
18342
|
+
async isBlocked(key) {
|
|
18343
|
+
const expiry = blocks.get(key);
|
|
18344
|
+
if (!expiry || expiry < Date.now()) {
|
|
18345
|
+
blocks.delete(key);
|
|
18346
|
+
return { blocked: false, ttlMs: 0 };
|
|
18347
|
+
}
|
|
18348
|
+
return { blocked: true, ttlMs: expiry - Date.now() };
|
|
18349
|
+
},
|
|
18350
|
+
async setBlock(key, durationSeconds) {
|
|
18351
|
+
blocks.set(key, Date.now() + durationSeconds * 1e3);
|
|
18352
|
+
},
|
|
18353
|
+
async reset(key) {
|
|
18354
|
+
windows.delete(key);
|
|
18355
|
+
blocks.delete(`block:${key}`);
|
|
18356
|
+
blocks.delete(key);
|
|
18357
|
+
}
|
|
18358
|
+
};
|
|
18359
|
+
}
|
|
18360
|
+
var defaultStore;
|
|
18361
|
+
function getDefaultStore() {
|
|
18362
|
+
if (!defaultStore) {
|
|
18363
|
+
defaultStore = createMemoryRateLimitStore();
|
|
18364
|
+
}
|
|
18365
|
+
return defaultStore;
|
|
18366
|
+
}
|
|
18367
|
+
async function checkRateLimit(operation, identifier, rule, options = {}) {
|
|
18368
|
+
const store = options.store ?? getDefaultStore();
|
|
18369
|
+
const limit = options.isAuthenticated && rule.authenticatedLimit ? rule.authenticatedLimit : rule.limit;
|
|
18370
|
+
const now = Date.now();
|
|
18371
|
+
const windowMs = rule.windowSeconds * 1e3;
|
|
18372
|
+
const resetAt = now + windowMs;
|
|
18373
|
+
const key = `ratelimit:${operation}:${identifier}`;
|
|
18374
|
+
const blockKey = `ratelimit:block:${operation}:${identifier}`;
|
|
18375
|
+
try {
|
|
18376
|
+
if (rule.blockDurationSeconds) {
|
|
18377
|
+
const blockStatus = await store.isBlocked(blockKey);
|
|
18378
|
+
if (blockStatus.blocked) {
|
|
18379
|
+
return {
|
|
18380
|
+
allowed: false,
|
|
18381
|
+
remaining: 0,
|
|
18382
|
+
resetAt: now + blockStatus.ttlMs,
|
|
18383
|
+
current: limit + 1,
|
|
18384
|
+
limit,
|
|
18385
|
+
retryAfterSeconds: Math.ceil(blockStatus.ttlMs / 1e3)
|
|
18386
|
+
};
|
|
18387
|
+
}
|
|
18388
|
+
}
|
|
18389
|
+
const { count } = await store.increment(key, windowMs, now);
|
|
18390
|
+
if (count > limit) {
|
|
18391
|
+
options.logger?.warn("Rate limit exceeded", {
|
|
18392
|
+
operation,
|
|
18393
|
+
identifier,
|
|
18394
|
+
current: count,
|
|
18395
|
+
limit
|
|
18396
|
+
});
|
|
18397
|
+
if (rule.blockDurationSeconds) {
|
|
18398
|
+
await store.setBlock(blockKey, rule.blockDurationSeconds);
|
|
18399
|
+
}
|
|
18400
|
+
return {
|
|
18401
|
+
allowed: false,
|
|
18402
|
+
remaining: 0,
|
|
18403
|
+
resetAt,
|
|
18404
|
+
current: count,
|
|
18405
|
+
limit,
|
|
18406
|
+
retryAfterSeconds: Math.ceil(windowMs / 1e3)
|
|
18407
|
+
};
|
|
18408
|
+
}
|
|
18409
|
+
return {
|
|
18410
|
+
allowed: true,
|
|
18411
|
+
remaining: limit - count,
|
|
18412
|
+
resetAt,
|
|
18413
|
+
current: count,
|
|
18414
|
+
limit,
|
|
18415
|
+
retryAfterSeconds: 0
|
|
18416
|
+
};
|
|
18417
|
+
} catch (error) {
|
|
18418
|
+
options.logger?.error("Rate limit check failed, allowing request", {
|
|
18419
|
+
error: error instanceof Error ? error.message : String(error),
|
|
18420
|
+
operation,
|
|
18421
|
+
identifier
|
|
18422
|
+
});
|
|
18423
|
+
return {
|
|
18424
|
+
allowed: true,
|
|
18425
|
+
remaining: limit,
|
|
18426
|
+
resetAt,
|
|
18427
|
+
current: 0,
|
|
18428
|
+
limit,
|
|
18429
|
+
retryAfterSeconds: 0
|
|
18430
|
+
};
|
|
18431
|
+
}
|
|
18432
|
+
}
|
|
18433
|
+
async function getRateLimitStatus(operation, identifier, rule, store) {
|
|
18434
|
+
const s = store ?? getDefaultStore();
|
|
18435
|
+
const key = `ratelimit:${operation}:${identifier}`;
|
|
18436
|
+
const now = Date.now();
|
|
18437
|
+
const windowMs = rule.windowSeconds * 1e3;
|
|
18438
|
+
try {
|
|
18439
|
+
const { count } = await s.increment(key, windowMs, now);
|
|
18440
|
+
return {
|
|
18441
|
+
allowed: count <= rule.limit,
|
|
18442
|
+
remaining: Math.max(0, rule.limit - count),
|
|
18443
|
+
resetAt: now + windowMs,
|
|
18444
|
+
current: count,
|
|
18445
|
+
limit: rule.limit,
|
|
18446
|
+
retryAfterSeconds: count > rule.limit ? Math.ceil(windowMs / 1e3) : 0
|
|
18447
|
+
};
|
|
18448
|
+
} catch {
|
|
18449
|
+
return null;
|
|
18450
|
+
}
|
|
18451
|
+
}
|
|
18452
|
+
async function resetRateLimitForKey(operation, identifier, store) {
|
|
18453
|
+
const s = store ?? getDefaultStore();
|
|
18454
|
+
const key = `ratelimit:${operation}:${identifier}`;
|
|
18455
|
+
const blockKey = `ratelimit:block:${operation}:${identifier}`;
|
|
18456
|
+
await s.reset(key);
|
|
18457
|
+
await s.reset(blockKey);
|
|
18458
|
+
}
|
|
18459
|
+
function buildRateLimitResponseHeaders(result) {
|
|
18460
|
+
const headers = {
|
|
18461
|
+
"X-RateLimit-Limit": String(result.limit),
|
|
18462
|
+
"X-RateLimit-Remaining": String(result.remaining),
|
|
18463
|
+
"X-RateLimit-Reset": String(Math.ceil(result.resetAt / 1e3))
|
|
18464
|
+
};
|
|
18465
|
+
if (!result.allowed) {
|
|
18466
|
+
headers["Retry-After"] = String(result.retryAfterSeconds);
|
|
18467
|
+
}
|
|
18468
|
+
return headers;
|
|
18469
|
+
}
|
|
18470
|
+
function resolveIdentifier(session, clientIp) {
|
|
18471
|
+
if (session?.user?.id) {
|
|
18472
|
+
return { identifier: `user:${session.user.id}`, isAuthenticated: true };
|
|
18473
|
+
}
|
|
18474
|
+
if (session?.user?.email) {
|
|
18475
|
+
return {
|
|
18476
|
+
identifier: `email:${session.user.email}`,
|
|
18477
|
+
isAuthenticated: true
|
|
18478
|
+
};
|
|
18479
|
+
}
|
|
18480
|
+
return { identifier: `ip:${clientIp ?? "unknown"}`, isAuthenticated: false };
|
|
18481
|
+
}
|
|
18482
|
+
|
|
18483
|
+
// src/auth/audit.ts
|
|
18484
|
+
var StandardAuditActions = {
|
|
18485
|
+
// Authentication
|
|
18486
|
+
LOGIN_SUCCESS: "auth.login.success",
|
|
18487
|
+
LOGIN_FAILURE: "auth.login.failure",
|
|
18488
|
+
LOGOUT: "auth.logout",
|
|
18489
|
+
SESSION_REFRESH: "auth.session.refresh",
|
|
18490
|
+
PASSWORD_CHANGE: "auth.password.change",
|
|
18491
|
+
PASSWORD_RESET: "auth.password.reset",
|
|
18492
|
+
// Billing
|
|
18493
|
+
CHECKOUT_START: "billing.checkout.start",
|
|
18494
|
+
CHECKOUT_COMPLETE: "billing.checkout.complete",
|
|
18495
|
+
SUBSCRIPTION_CREATE: "billing.subscription.create",
|
|
18496
|
+
SUBSCRIPTION_CANCEL: "billing.subscription.cancel",
|
|
18497
|
+
SUBSCRIPTION_UPDATE: "billing.subscription.update",
|
|
18498
|
+
PAYMENT_FAILED: "billing.payment.failed",
|
|
18499
|
+
// Admin
|
|
18500
|
+
ADMIN_LOGIN: "admin.login",
|
|
18501
|
+
ADMIN_USER_VIEW: "admin.user.view",
|
|
18502
|
+
ADMIN_USER_UPDATE: "admin.user.update",
|
|
18503
|
+
ADMIN_CONFIG_CHANGE: "admin.config.change",
|
|
18504
|
+
// Security Events
|
|
18505
|
+
RATE_LIMIT_EXCEEDED: "security.rate_limit.exceeded",
|
|
18506
|
+
INVALID_INPUT: "security.input.invalid",
|
|
18507
|
+
UNAUTHORIZED_ACCESS: "security.access.unauthorized",
|
|
18508
|
+
OWNERSHIP_VIOLATION: "security.ownership.violation",
|
|
18509
|
+
WEBHOOK_SIGNATURE_INVALID: "security.webhook.signature_invalid",
|
|
18510
|
+
// Data
|
|
18511
|
+
DATA_EXPORT: "data.export",
|
|
18512
|
+
DATA_DELETE: "data.delete",
|
|
18513
|
+
DATA_UPDATE: "data.update"
|
|
18514
|
+
};
|
|
18515
|
+
function extractAuditIp(request) {
|
|
18516
|
+
if (!request) return void 0;
|
|
18517
|
+
const headers = [
|
|
18518
|
+
"cf-connecting-ip",
|
|
18519
|
+
// Cloudflare
|
|
18520
|
+
"x-real-ip",
|
|
18521
|
+
// Nginx
|
|
18522
|
+
"x-forwarded-for",
|
|
18523
|
+
// Standard proxy
|
|
18524
|
+
"x-client-ip"
|
|
18525
|
+
// Apache
|
|
18526
|
+
];
|
|
18527
|
+
for (const header of headers) {
|
|
18528
|
+
const value = request.headers.get(header);
|
|
18529
|
+
if (value) {
|
|
18530
|
+
return value.split(",")[0]?.trim();
|
|
18531
|
+
}
|
|
18532
|
+
}
|
|
18533
|
+
return void 0;
|
|
18534
|
+
}
|
|
18535
|
+
function extractAuditUserAgent(request) {
|
|
18536
|
+
return request?.headers.get("user-agent") ?? void 0;
|
|
18537
|
+
}
|
|
18538
|
+
function extractAuditRequestId(request) {
|
|
18539
|
+
return request?.headers.get("x-request-id") ?? crypto.randomUUID();
|
|
18540
|
+
}
|
|
18541
|
+
function createAuditActor(session) {
|
|
18542
|
+
if (!session?.user) {
|
|
18543
|
+
return { id: "anonymous", type: "anonymous" };
|
|
18544
|
+
}
|
|
18545
|
+
return {
|
|
18546
|
+
id: session.user.id ?? session.user.email ?? "unknown",
|
|
18547
|
+
email: session.user.email ?? void 0,
|
|
18548
|
+
type: "user"
|
|
18549
|
+
};
|
|
18550
|
+
}
|
|
18551
|
+
var defaultLogger = {
|
|
18552
|
+
info: (msg, meta) => console.log(msg, meta ? JSON.stringify(meta) : ""),
|
|
18553
|
+
warn: (msg, meta) => console.warn(msg, meta ? JSON.stringify(meta) : ""),
|
|
18554
|
+
error: (msg, meta) => console.error(msg, meta ? JSON.stringify(meta) : "")
|
|
18555
|
+
};
|
|
18556
|
+
function createAuditLogger(options = {}) {
|
|
18557
|
+
const { persist, logger = defaultLogger } = options;
|
|
18558
|
+
async function log(event, request) {
|
|
18559
|
+
const record = {
|
|
18560
|
+
id: crypto.randomUUID(),
|
|
18561
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18562
|
+
ip: extractAuditIp(request),
|
|
18563
|
+
userAgent: extractAuditUserAgent(request),
|
|
18564
|
+
requestId: extractAuditRequestId(request),
|
|
18565
|
+
...event
|
|
18566
|
+
};
|
|
18567
|
+
const logFn = event.outcome === "failure" || event.outcome === "blocked" ? logger.warn : logger.info;
|
|
18568
|
+
logFn(`[AUDIT] ${event.action}`, {
|
|
18569
|
+
auditId: record.id,
|
|
18570
|
+
actor: record.actor,
|
|
18571
|
+
action: record.action,
|
|
18572
|
+
resource: record.resource,
|
|
18573
|
+
outcome: record.outcome,
|
|
18574
|
+
ip: record.ip,
|
|
18575
|
+
metadata: record.metadata,
|
|
18576
|
+
reason: record.reason
|
|
18577
|
+
});
|
|
18578
|
+
if (persist) {
|
|
18579
|
+
try {
|
|
18580
|
+
await persist(record);
|
|
18581
|
+
} catch (error) {
|
|
18582
|
+
logger.error("Failed to persist audit log", {
|
|
18583
|
+
error: error instanceof Error ? error.message : String(error),
|
|
18584
|
+
auditId: record.id
|
|
18585
|
+
});
|
|
18586
|
+
}
|
|
18587
|
+
}
|
|
18588
|
+
return record;
|
|
18589
|
+
}
|
|
18590
|
+
function createTimedAudit(event, request) {
|
|
18591
|
+
const startTime = Date.now();
|
|
18592
|
+
return {
|
|
18593
|
+
success: async (metadata) => {
|
|
18594
|
+
return log(
|
|
18595
|
+
{
|
|
18596
|
+
...event,
|
|
18597
|
+
outcome: "success",
|
|
18598
|
+
metadata: {
|
|
18599
|
+
...event.metadata,
|
|
18600
|
+
...metadata,
|
|
18601
|
+
durationMs: Date.now() - startTime
|
|
18602
|
+
}
|
|
18603
|
+
},
|
|
18604
|
+
request
|
|
18605
|
+
);
|
|
18606
|
+
},
|
|
18607
|
+
failure: async (reason, metadata) => {
|
|
18608
|
+
return log(
|
|
18609
|
+
{
|
|
18610
|
+
...event,
|
|
18611
|
+
outcome: "failure",
|
|
18612
|
+
reason,
|
|
18613
|
+
metadata: {
|
|
18614
|
+
...event.metadata,
|
|
18615
|
+
...metadata,
|
|
18616
|
+
durationMs: Date.now() - startTime
|
|
18617
|
+
}
|
|
18618
|
+
},
|
|
18619
|
+
request
|
|
18620
|
+
);
|
|
18621
|
+
},
|
|
18622
|
+
blocked: async (reason, metadata) => {
|
|
18623
|
+
return log(
|
|
18624
|
+
{
|
|
18625
|
+
...event,
|
|
18626
|
+
outcome: "blocked",
|
|
18627
|
+
reason,
|
|
18628
|
+
metadata: {
|
|
18629
|
+
...event.metadata,
|
|
18630
|
+
...metadata,
|
|
18631
|
+
durationMs: Date.now() - startTime
|
|
18632
|
+
}
|
|
18633
|
+
},
|
|
18634
|
+
request
|
|
18635
|
+
);
|
|
18636
|
+
}
|
|
18637
|
+
};
|
|
18638
|
+
}
|
|
18639
|
+
return { log, createTimedAudit };
|
|
18640
|
+
}
|
|
18641
|
+
|
|
17759
18642
|
// src/http/health.ts
|
|
17760
18643
|
function createHealthEndpoints(platform, options = {}) {
|
|
17761
18644
|
const {
|
|
@@ -26311,7 +27194,7 @@ CREATE INDEX IF NOT EXISTS idx_${executionsTable}_started ON ${executionsTable}(
|
|
|
26311
27194
|
};
|
|
26312
27195
|
|
|
26313
27196
|
// src/adapters/webhook/HttpWebhook.ts
|
|
26314
|
-
import { createHmac as createHmac4, timingSafeEqual, randomBytes as randomBytes35 } from "crypto";
|
|
27197
|
+
import { createHmac as createHmac4, timingSafeEqual as timingSafeEqual2, randomBytes as randomBytes35 } from "crypto";
|
|
26315
27198
|
var HttpWebhook = class {
|
|
26316
27199
|
db;
|
|
26317
27200
|
queue;
|
|
@@ -26660,7 +27543,7 @@ var HttpWebhook = class {
|
|
|
26660
27543
|
if (providedBuffer.length !== expectedBuffer.length) {
|
|
26661
27544
|
return { valid: false, error: "Invalid signature" };
|
|
26662
27545
|
}
|
|
26663
|
-
if (!
|
|
27546
|
+
if (!timingSafeEqual2(providedBuffer, expectedBuffer)) {
|
|
26664
27547
|
return { valid: false, error: "Invalid signature" };
|
|
26665
27548
|
}
|
|
26666
27549
|
} catch {
|
|
@@ -29716,6 +30599,272 @@ function generateId2() {
|
|
|
29716
30599
|
return randomBytes36(8).toString("hex") + Date.now().toString(36);
|
|
29717
30600
|
}
|
|
29718
30601
|
|
|
30602
|
+
// src/env.ts
|
|
30603
|
+
function getRequiredEnv(key) {
|
|
30604
|
+
const value = process.env[key];
|
|
30605
|
+
if (!value) {
|
|
30606
|
+
throw new Error(`Missing required environment variable: ${key}`);
|
|
30607
|
+
}
|
|
30608
|
+
return value;
|
|
30609
|
+
}
|
|
30610
|
+
function getOptionalEnv(key, defaultValue) {
|
|
30611
|
+
return process.env[key] || defaultValue;
|
|
30612
|
+
}
|
|
30613
|
+
function getBoolEnv(key, defaultValue = false) {
|
|
30614
|
+
const value = process.env[key];
|
|
30615
|
+
if (value === void 0 || value === "") return defaultValue;
|
|
30616
|
+
return value === "true" || value === "1";
|
|
30617
|
+
}
|
|
30618
|
+
function getIntEnv(key, defaultValue) {
|
|
30619
|
+
const value = process.env[key];
|
|
30620
|
+
if (value === void 0 || value === "") return defaultValue;
|
|
30621
|
+
const parsed = parseInt(value, 10);
|
|
30622
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
30623
|
+
}
|
|
30624
|
+
function validateEnvVars(config) {
|
|
30625
|
+
const result = checkEnvVars(config);
|
|
30626
|
+
if (!result.valid) {
|
|
30627
|
+
const lines = [];
|
|
30628
|
+
if (result.missing.length > 0) {
|
|
30629
|
+
lines.push(
|
|
30630
|
+
"Missing required environment variables:",
|
|
30631
|
+
...result.missing.map((v) => ` - ${v}`)
|
|
30632
|
+
);
|
|
30633
|
+
}
|
|
30634
|
+
if (result.missingOneOf.length > 0) {
|
|
30635
|
+
for (const group of result.missingOneOf) {
|
|
30636
|
+
lines.push(`Missing one of: ${group.join(" | ")}`);
|
|
30637
|
+
}
|
|
30638
|
+
}
|
|
30639
|
+
if (result.invalid.length > 0) {
|
|
30640
|
+
lines.push(
|
|
30641
|
+
"Invalid environment variables:",
|
|
30642
|
+
...result.invalid.map((v) => ` - ${v.key}: ${v.reason}`)
|
|
30643
|
+
);
|
|
30644
|
+
}
|
|
30645
|
+
throw new Error(lines.join("\n"));
|
|
30646
|
+
}
|
|
30647
|
+
}
|
|
30648
|
+
function checkEnvVars(config) {
|
|
30649
|
+
const missing = [];
|
|
30650
|
+
const invalid = [];
|
|
30651
|
+
const missingOneOf = [];
|
|
30652
|
+
if (config.required) {
|
|
30653
|
+
for (const key of config.required) {
|
|
30654
|
+
if (!process.env[key]) {
|
|
30655
|
+
missing.push(key);
|
|
30656
|
+
}
|
|
30657
|
+
}
|
|
30658
|
+
}
|
|
30659
|
+
if (config.requireOneOf) {
|
|
30660
|
+
for (const group of config.requireOneOf) {
|
|
30661
|
+
const hasAny = group.some((key) => !!process.env[key]);
|
|
30662
|
+
if (!hasAny) {
|
|
30663
|
+
missingOneOf.push(group);
|
|
30664
|
+
}
|
|
30665
|
+
}
|
|
30666
|
+
}
|
|
30667
|
+
if (config.validators) {
|
|
30668
|
+
for (const [key, validator] of Object.entries(config.validators)) {
|
|
30669
|
+
const value = process.env[key];
|
|
30670
|
+
if (value) {
|
|
30671
|
+
const result = validator(value);
|
|
30672
|
+
if (result !== true) {
|
|
30673
|
+
invalid.push({ key, reason: result });
|
|
30674
|
+
}
|
|
30675
|
+
}
|
|
30676
|
+
}
|
|
30677
|
+
}
|
|
30678
|
+
return {
|
|
30679
|
+
valid: missing.length === 0 && invalid.length === 0 && missingOneOf.length === 0,
|
|
30680
|
+
missing,
|
|
30681
|
+
invalid,
|
|
30682
|
+
missingOneOf
|
|
30683
|
+
};
|
|
30684
|
+
}
|
|
30685
|
+
function getEnvSummary(keys) {
|
|
30686
|
+
const summary = {};
|
|
30687
|
+
for (const key of keys) {
|
|
30688
|
+
summary[key] = !!process.env[key];
|
|
30689
|
+
}
|
|
30690
|
+
return summary;
|
|
30691
|
+
}
|
|
30692
|
+
|
|
30693
|
+
// src/app-logger.ts
|
|
30694
|
+
var LEVEL_PRIORITY2 = {
|
|
30695
|
+
debug: 0,
|
|
30696
|
+
info: 1,
|
|
30697
|
+
warn: 2,
|
|
30698
|
+
error: 3
|
|
30699
|
+
};
|
|
30700
|
+
function defaultOutput(level, message, context) {
|
|
30701
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
30702
|
+
if (isProduction) {
|
|
30703
|
+
const entry = { level, message, ...context };
|
|
30704
|
+
const method = level === "error" ? "error" : level === "warn" ? "warn" : "log";
|
|
30705
|
+
console[method](JSON.stringify(entry));
|
|
30706
|
+
} else {
|
|
30707
|
+
const prefix = `[${level.toUpperCase()}]`;
|
|
30708
|
+
const ctx = Object.keys(context).length > 0 ? " " + JSON.stringify(context) : "";
|
|
30709
|
+
const method = level === "error" ? "error" : level === "warn" ? "warn" : level === "debug" ? "debug" : "log";
|
|
30710
|
+
console[method](`${prefix} ${message}${ctx}`);
|
|
30711
|
+
}
|
|
30712
|
+
}
|
|
30713
|
+
var AppLoggerImpl = class _AppLoggerImpl {
|
|
30714
|
+
baseContext;
|
|
30715
|
+
minLevelPriority;
|
|
30716
|
+
outputFn;
|
|
30717
|
+
onErrorFn;
|
|
30718
|
+
constructor(baseContext, options) {
|
|
30719
|
+
this.baseContext = baseContext;
|
|
30720
|
+
this.minLevelPriority = LEVEL_PRIORITY2[options.minLevel] ?? 0;
|
|
30721
|
+
this.outputFn = options.output;
|
|
30722
|
+
this.onErrorFn = options.onError;
|
|
30723
|
+
}
|
|
30724
|
+
log(level, message, context) {
|
|
30725
|
+
if ((LEVEL_PRIORITY2[level] ?? 0) < this.minLevelPriority) return;
|
|
30726
|
+
const merged = {
|
|
30727
|
+
...this.baseContext,
|
|
30728
|
+
...context,
|
|
30729
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
30730
|
+
};
|
|
30731
|
+
this.outputFn(level, message, merged);
|
|
30732
|
+
}
|
|
30733
|
+
debug(message, context) {
|
|
30734
|
+
this.log("debug", message, context);
|
|
30735
|
+
}
|
|
30736
|
+
info(message, context) {
|
|
30737
|
+
this.log("info", message, context);
|
|
30738
|
+
}
|
|
30739
|
+
warn(message, context) {
|
|
30740
|
+
this.log("warn", message, context);
|
|
30741
|
+
}
|
|
30742
|
+
error(message, context) {
|
|
30743
|
+
this.log("error", message, context);
|
|
30744
|
+
if (this.onErrorFn) {
|
|
30745
|
+
try {
|
|
30746
|
+
this.onErrorFn(message, { ...this.baseContext, ...context });
|
|
30747
|
+
} catch {
|
|
30748
|
+
}
|
|
30749
|
+
}
|
|
30750
|
+
}
|
|
30751
|
+
child(context) {
|
|
30752
|
+
return new _AppLoggerImpl(
|
|
30753
|
+
{ ...this.baseContext, ...context },
|
|
30754
|
+
{
|
|
30755
|
+
minLevel: Object.entries(LEVEL_PRIORITY2).find(
|
|
30756
|
+
([, v]) => v === this.minLevelPriority
|
|
30757
|
+
)?.[0] ?? "debug",
|
|
30758
|
+
output: this.outputFn,
|
|
30759
|
+
onError: this.onErrorFn
|
|
30760
|
+
}
|
|
30761
|
+
);
|
|
30762
|
+
}
|
|
30763
|
+
forRequest(request, operation) {
|
|
30764
|
+
const requestId = request.headers.get("x-request-id") ?? request.headers.get("x-correlation-id") ?? crypto.randomUUID();
|
|
30765
|
+
let path;
|
|
30766
|
+
try {
|
|
30767
|
+
path = new URL(request.url).pathname;
|
|
30768
|
+
} catch {
|
|
30769
|
+
path = request.url;
|
|
30770
|
+
}
|
|
30771
|
+
return this.child({
|
|
30772
|
+
requestId,
|
|
30773
|
+
operation,
|
|
30774
|
+
method: request.method,
|
|
30775
|
+
path
|
|
30776
|
+
});
|
|
30777
|
+
}
|
|
30778
|
+
forJob(jobType, jobId, context) {
|
|
30779
|
+
return this.child({ jobType, jobId, ...context });
|
|
30780
|
+
}
|
|
30781
|
+
async timed(operation, fn, context) {
|
|
30782
|
+
const opLogger = this.child({ operation, ...context });
|
|
30783
|
+
opLogger.debug("Operation started");
|
|
30784
|
+
const start = Date.now();
|
|
30785
|
+
try {
|
|
30786
|
+
const result = await fn();
|
|
30787
|
+
opLogger.info("Operation completed", {
|
|
30788
|
+
durationMs: Date.now() - start
|
|
30789
|
+
});
|
|
30790
|
+
return result;
|
|
30791
|
+
} catch (error) {
|
|
30792
|
+
opLogger.error("Operation failed", {
|
|
30793
|
+
durationMs: Date.now() - start,
|
|
30794
|
+
error: error instanceof Error ? error.message : String(error)
|
|
30795
|
+
});
|
|
30796
|
+
throw error;
|
|
30797
|
+
}
|
|
30798
|
+
}
|
|
30799
|
+
};
|
|
30800
|
+
function createAppLogger(options) {
|
|
30801
|
+
const {
|
|
30802
|
+
service,
|
|
30803
|
+
minLevel = process.env.NODE_ENV === "production" ? "info" : "debug",
|
|
30804
|
+
output = defaultOutput,
|
|
30805
|
+
onError
|
|
30806
|
+
} = options;
|
|
30807
|
+
return new AppLoggerImpl({ service }, { minLevel, output, onError });
|
|
30808
|
+
}
|
|
30809
|
+
|
|
30810
|
+
// src/redis-client.ts
|
|
30811
|
+
import Redis from "ioredis";
|
|
30812
|
+
function createRedisClient(options) {
|
|
30813
|
+
const {
|
|
30814
|
+
url,
|
|
30815
|
+
keyPrefix,
|
|
30816
|
+
maxRetries = 3,
|
|
30817
|
+
lazyConnect = true,
|
|
30818
|
+
silent = false
|
|
30819
|
+
} = options;
|
|
30820
|
+
const logger = options.logger ?? {
|
|
30821
|
+
error: console.error
|
|
30822
|
+
};
|
|
30823
|
+
const client = new Redis(url, {
|
|
30824
|
+
keyPrefix,
|
|
30825
|
+
maxRetriesPerRequest: maxRetries,
|
|
30826
|
+
retryStrategy(times) {
|
|
30827
|
+
if (times > maxRetries) return null;
|
|
30828
|
+
return Math.min(times * 200, 2e3);
|
|
30829
|
+
},
|
|
30830
|
+
lazyConnect
|
|
30831
|
+
});
|
|
30832
|
+
if (!silent) {
|
|
30833
|
+
client.on("error", (err) => {
|
|
30834
|
+
logger.error("[redis] Connection error", { message: err.message });
|
|
30835
|
+
});
|
|
30836
|
+
if (logger.info) {
|
|
30837
|
+
const logInfo = logger.info;
|
|
30838
|
+
client.on("connect", () => {
|
|
30839
|
+
logInfo("[redis] Connected");
|
|
30840
|
+
});
|
|
30841
|
+
}
|
|
30842
|
+
}
|
|
30843
|
+
if (lazyConnect) {
|
|
30844
|
+
client.connect().catch(() => {
|
|
30845
|
+
});
|
|
30846
|
+
}
|
|
30847
|
+
return client;
|
|
30848
|
+
}
|
|
30849
|
+
var sharedClient = null;
|
|
30850
|
+
function getSharedRedis() {
|
|
30851
|
+
if (sharedClient) return sharedClient;
|
|
30852
|
+
const url = process.env.REDIS_URL;
|
|
30853
|
+
if (!url) return null;
|
|
30854
|
+
sharedClient = createRedisClient({
|
|
30855
|
+
url,
|
|
30856
|
+
keyPrefix: process.env.REDIS_KEY_PREFIX,
|
|
30857
|
+
silent: process.env.NODE_ENV === "test"
|
|
30858
|
+
});
|
|
30859
|
+
return sharedClient;
|
|
30860
|
+
}
|
|
30861
|
+
async function closeSharedRedis() {
|
|
30862
|
+
if (sharedClient) {
|
|
30863
|
+
await sharedClient.quit();
|
|
30864
|
+
sharedClient = null;
|
|
30865
|
+
}
|
|
30866
|
+
}
|
|
30867
|
+
|
|
29719
30868
|
// src/migrations/Migrator.ts
|
|
29720
30869
|
var DEFAULT_CONFIG = {
|
|
29721
30870
|
tableName: "_migrations",
|
|
@@ -30492,6 +31641,7 @@ export {
|
|
|
30492
31641
|
CircuitBreakerRegistry,
|
|
30493
31642
|
CircuitOpenError,
|
|
30494
31643
|
CommonApiErrors,
|
|
31644
|
+
CommonRateLimits,
|
|
30495
31645
|
ConsoleEmail,
|
|
30496
31646
|
ConsoleLogger,
|
|
30497
31647
|
CronPresets,
|
|
@@ -30508,17 +31658,21 @@ export {
|
|
|
30508
31658
|
DatabaseNotification,
|
|
30509
31659
|
DatabasePromptStore,
|
|
30510
31660
|
DatabaseProviderSchema,
|
|
31661
|
+
DateRangeSchema,
|
|
30511
31662
|
DefaultTimeouts,
|
|
30512
31663
|
EmailConfigSchema,
|
|
30513
31664
|
EmailProviderSchema,
|
|
31665
|
+
EmailSchema,
|
|
30514
31666
|
EnvSecrets,
|
|
30515
31667
|
FallbackStrategies,
|
|
30516
31668
|
GenericOIDCAuthSSO,
|
|
30517
31669
|
GoogleAIAdapter,
|
|
30518
31670
|
HTML_TAG_PATTERN,
|
|
30519
31671
|
HttpWebhook,
|
|
31672
|
+
KEYCLOAK_DEFAULT_ROLES,
|
|
30520
31673
|
LogLevelSchema,
|
|
30521
31674
|
LoggingConfigSchema,
|
|
31675
|
+
LoginSchema,
|
|
30522
31676
|
MemoryAI,
|
|
30523
31677
|
MemoryAIUsage,
|
|
30524
31678
|
MemoryAuditLog,
|
|
@@ -30557,7 +31711,11 @@ export {
|
|
|
30557
31711
|
ObservabilityConfigSchema,
|
|
30558
31712
|
OpenAIAdapter,
|
|
30559
31713
|
PG_ERROR_MAP,
|
|
31714
|
+
PaginationSchema,
|
|
31715
|
+
PasswordSchema,
|
|
30560
31716
|
PaymentErrorMessages,
|
|
31717
|
+
PersonNameSchema,
|
|
31718
|
+
PhoneSchema,
|
|
30561
31719
|
PineconeRAG,
|
|
30562
31720
|
PlatformConfigSchema,
|
|
30563
31721
|
PostgresDatabase,
|
|
@@ -30577,9 +31735,14 @@ export {
|
|
|
30577
31735
|
RetryPredicates,
|
|
30578
31736
|
S3Storage,
|
|
30579
31737
|
SQL,
|
|
31738
|
+
SearchQuerySchema,
|
|
30580
31739
|
SecurityConfigSchema,
|
|
30581
31740
|
SecurityHeaderPresets,
|
|
31741
|
+
SignupSchema,
|
|
31742
|
+
SlugSchema,
|
|
30582
31743
|
SmtpEmail,
|
|
31744
|
+
StandardAuditActions,
|
|
31745
|
+
StandardRateLimitPresets,
|
|
30583
31746
|
StorageConfigSchema,
|
|
30584
31747
|
StorageProviderSchema,
|
|
30585
31748
|
StripePayment,
|
|
@@ -30595,16 +31758,32 @@ export {
|
|
|
30595
31758
|
UpstashCache,
|
|
30596
31759
|
WeaviateRAG,
|
|
30597
31760
|
WebhookEventTypes,
|
|
31761
|
+
WrapperPresets,
|
|
31762
|
+
buildAllowlist,
|
|
31763
|
+
buildAuthCookies,
|
|
31764
|
+
buildErrorBody,
|
|
31765
|
+
buildKeycloakCallbacks,
|
|
30598
31766
|
buildPagination,
|
|
31767
|
+
buildRateLimitHeaders,
|
|
31768
|
+
buildRateLimitResponseHeaders,
|
|
31769
|
+
buildRedirectCallback,
|
|
31770
|
+
buildTokenRefreshParams,
|
|
30599
31771
|
calculateBackoff,
|
|
30600
31772
|
calculateRetryDelay,
|
|
31773
|
+
checkEnvVars,
|
|
31774
|
+
checkRateLimit,
|
|
30601
31775
|
classifyError,
|
|
31776
|
+
closeSharedRedis,
|
|
30602
31777
|
composeHookRegistries,
|
|
31778
|
+
constantTimeEqual,
|
|
30603
31779
|
containsHtml,
|
|
30604
31780
|
containsUrls,
|
|
30605
31781
|
correlationContext,
|
|
30606
31782
|
createAIError,
|
|
30607
31783
|
createAnthropicAdapter,
|
|
31784
|
+
createAppLogger,
|
|
31785
|
+
createAuditActor,
|
|
31786
|
+
createAuditLogger,
|
|
30608
31787
|
createAuthError,
|
|
30609
31788
|
createBulkhead,
|
|
30610
31789
|
createCacheMiddleware,
|
|
@@ -30615,6 +31794,7 @@ export {
|
|
|
30615
31794
|
createErrorReport,
|
|
30616
31795
|
createExpressHealthHandlers,
|
|
30617
31796
|
createExpressMetricsHandler,
|
|
31797
|
+
createFeatureFlags,
|
|
30618
31798
|
createGoogleAIAdapter,
|
|
30619
31799
|
createHealthEndpoints,
|
|
30620
31800
|
createHealthServer,
|
|
@@ -30622,6 +31802,7 @@ export {
|
|
|
30622
31802
|
createIpKeyGenerator,
|
|
30623
31803
|
createJobContext,
|
|
30624
31804
|
createLoggingMiddleware,
|
|
31805
|
+
createMemoryRateLimitStore,
|
|
30625
31806
|
createMetricsEndpoint,
|
|
30626
31807
|
createMetricsMiddleware,
|
|
30627
31808
|
createMetricsServer,
|
|
@@ -30635,8 +31816,10 @@ export {
|
|
|
30635
31816
|
createPlatform,
|
|
30636
31817
|
createPlatformAsync,
|
|
30637
31818
|
createRateLimitMiddleware,
|
|
31819
|
+
createRedisClient,
|
|
30638
31820
|
createRequestContext,
|
|
30639
31821
|
createRequestIdMiddleware,
|
|
31822
|
+
createSafeTextSchema,
|
|
30640
31823
|
createScopedMetrics,
|
|
30641
31824
|
createSlowQueryMiddleware,
|
|
30642
31825
|
createSsoOidcConfigsTable,
|
|
@@ -30653,8 +31836,13 @@ export {
|
|
|
30653
31836
|
defangUrl,
|
|
30654
31837
|
defineMigration,
|
|
30655
31838
|
describeCron,
|
|
31839
|
+
detectStage,
|
|
30656
31840
|
enterpriseMigrations,
|
|
30657
31841
|
escapeHtml,
|
|
31842
|
+
extractAuditIp,
|
|
31843
|
+
extractAuditRequestId,
|
|
31844
|
+
extractAuditUserAgent,
|
|
31845
|
+
extractClientIp,
|
|
30658
31846
|
filterChannelsByPreferences,
|
|
30659
31847
|
formatAmount,
|
|
30660
31848
|
generateAuditId,
|
|
@@ -30672,37 +31860,58 @@ export {
|
|
|
30672
31860
|
generateVersion,
|
|
30673
31861
|
generateWebhookId,
|
|
30674
31862
|
generateWebhookSecret,
|
|
31863
|
+
getBoolEnv,
|
|
30675
31864
|
getContext,
|
|
30676
|
-
getCorrelationId,
|
|
31865
|
+
getCorrelationId2 as getCorrelationId,
|
|
30677
31866
|
getDefaultConfig,
|
|
31867
|
+
getEndSessionEndpoint,
|
|
30678
31868
|
getEnterpriseMigrations,
|
|
31869
|
+
getEnvSummary,
|
|
31870
|
+
getIntEnv,
|
|
30679
31871
|
getLogMeta,
|
|
30680
31872
|
getNextCronRun,
|
|
31873
|
+
getOptionalEnv,
|
|
31874
|
+
getRateLimitStatus,
|
|
30681
31875
|
getRequestId,
|
|
31876
|
+
getRequiredEnv,
|
|
31877
|
+
getSharedRedis,
|
|
30682
31878
|
getTenantId,
|
|
31879
|
+
getTokenEndpoint,
|
|
30683
31880
|
getTraceId,
|
|
30684
31881
|
getUserId,
|
|
31882
|
+
hasAllRoles,
|
|
31883
|
+
hasAnyRole,
|
|
31884
|
+
hasRole,
|
|
30685
31885
|
isAIError,
|
|
31886
|
+
isAllowlisted,
|
|
30686
31887
|
isApiError,
|
|
30687
31888
|
isAuthError,
|
|
30688
31889
|
isInContext,
|
|
30689
31890
|
isInQuietHours,
|
|
30690
31891
|
isPaymentError,
|
|
31892
|
+
isTokenExpired,
|
|
30691
31893
|
isValidCron,
|
|
30692
31894
|
loadConfig,
|
|
30693
31895
|
matchAction,
|
|
30694
31896
|
matchEventType,
|
|
31897
|
+
parseKeycloakRoles,
|
|
30695
31898
|
raceTimeout,
|
|
31899
|
+
refreshKeycloakToken,
|
|
31900
|
+
resetRateLimitForKey,
|
|
31901
|
+
resolveIdentifier,
|
|
31902
|
+
resolveRateLimitIdentifier,
|
|
30696
31903
|
retryable,
|
|
30697
31904
|
runWithContext,
|
|
30698
31905
|
runWithContextAsync,
|
|
30699
31906
|
safeValidateConfig,
|
|
31907
|
+
sanitizeApiError,
|
|
30700
31908
|
sanitizeForEmail,
|
|
30701
31909
|
sqlMigration,
|
|
30702
31910
|
stripHtml,
|
|
30703
31911
|
timedHealthCheck,
|
|
30704
31912
|
toHealthCheckResult,
|
|
30705
31913
|
validateConfig,
|
|
31914
|
+
validateEnvVars,
|
|
30706
31915
|
withCorrelation,
|
|
30707
31916
|
withCorrelationAsync,
|
|
30708
31917
|
withFallback,
|