@fonderie/auth 7.17.1 → 7.18.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/brain/outcomes.md +1 -0
- package/brain/signatures.md +23 -1
- package/dist/index.cjs +107 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +101 -17
- package/dist/index.js.map +1 -1
- package/dist/middlewares/index.cjs +12 -7
- package/dist/middlewares/index.cjs.map +1 -1
- package/dist/middlewares/index.d.cts +1 -1
- package/dist/middlewares/index.d.ts +1 -1
- package/dist/middlewares/index.js +12 -7
- package/dist/middlewares/index.js.map +1 -1
- package/dist/migrations/sql/021_session_client_kind.sql +6 -0
- package/dist/{session-C-ptRylj.d.cts → session-B-IgXFu8.d.cts} +32 -2
- package/dist/{session-C-ptRylj.d.ts → session-B-IgXFu8.d.ts} +32 -2
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -3,8 +3,8 @@ export { IMfaChallenge, ISession } from './types.cjs';
|
|
|
3
3
|
import { IFonderieModule, IAdminDescription, IReadinessProblem, IFonderieApp, IDefaultTemplate, IAdminRoute } from '@fonderie/core';
|
|
4
4
|
import { IStoreAdapter } from '@fonderie/store';
|
|
5
5
|
import { EventBus } from '@fonderie/events';
|
|
6
|
-
import { I as IAuthConfig, a as IRequestLocation } from './session-
|
|
7
|
-
export { A as AUTH_CONFIG_KEYS, b as AuthLimitedRoute, c as AuthMessageKey, d as
|
|
6
|
+
import { I as IAuthConfig, a as IRequestLocation } from './session-B-IgXFu8.cjs';
|
|
7
|
+
export { A as AUTH_CONFIG_KEYS, b as AuthLimitedRoute, c as AuthMessageKey, C as CLIENT_KINDS, d as ClientKind, e as IAuthRateLimitConfig, f as IAuthRuntimeConfig, g as IAuthSecrets, h as IDataExportContributor, i as ILocationRequest, j as ISessionPolicy, L as LocationResolver, M as MESSAGE_KEYS, S as SESSION_POLICY_PRESETS, k as buildAuthAccountLimiter, l as buildAuthIpLimiter, m as clientKindOf, n as configForClient, r as readAuthRuntimeConfig, o as resolveLocation, s as sanitizeLocation, p as sessionPolicyConfigKeys, w as withSession } from './session-B-IgXFu8.cjs';
|
|
8
8
|
import { IUserDTO } from './dtos/user.cjs';
|
|
9
9
|
export { toUserDTO } from './dtos/user.cjs';
|
|
10
10
|
export { requireAuth, validate } from '@fonderie/core/middlewares';
|
|
@@ -50,6 +50,8 @@ interface ISessionDTO {
|
|
|
50
50
|
location: IRequestLocation | null;
|
|
51
51
|
createdAt: string;
|
|
52
52
|
expiresAt: string;
|
|
53
|
+
/** The platform declared at sign-in (mobile | desktop | web), or null. */
|
|
54
|
+
clientKind: string | null;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
declare const registerSchema: z.ZodUnion<readonly [z.ZodObject<{
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ export { IMfaChallenge, ISession } from './types.js';
|
|
|
3
3
|
import { IFonderieModule, IAdminDescription, IReadinessProblem, IFonderieApp, IDefaultTemplate, IAdminRoute } from '@fonderie/core';
|
|
4
4
|
import { IStoreAdapter } from '@fonderie/store';
|
|
5
5
|
import { EventBus } from '@fonderie/events';
|
|
6
|
-
import { I as IAuthConfig, a as IRequestLocation } from './session-
|
|
7
|
-
export { A as AUTH_CONFIG_KEYS, b as AuthLimitedRoute, c as AuthMessageKey, d as
|
|
6
|
+
import { I as IAuthConfig, a as IRequestLocation } from './session-B-IgXFu8.js';
|
|
7
|
+
export { A as AUTH_CONFIG_KEYS, b as AuthLimitedRoute, c as AuthMessageKey, C as CLIENT_KINDS, d as ClientKind, e as IAuthRateLimitConfig, f as IAuthRuntimeConfig, g as IAuthSecrets, h as IDataExportContributor, i as ILocationRequest, j as ISessionPolicy, L as LocationResolver, M as MESSAGE_KEYS, S as SESSION_POLICY_PRESETS, k as buildAuthAccountLimiter, l as buildAuthIpLimiter, m as clientKindOf, n as configForClient, r as readAuthRuntimeConfig, o as resolveLocation, s as sanitizeLocation, p as sessionPolicyConfigKeys, w as withSession } from './session-B-IgXFu8.js';
|
|
8
8
|
import { IUserDTO } from './dtos/user.js';
|
|
9
9
|
export { toUserDTO } from './dtos/user.js';
|
|
10
10
|
export { requireAuth, validate } from '@fonderie/core/middlewares';
|
|
@@ -50,6 +50,8 @@ interface ISessionDTO {
|
|
|
50
50
|
location: IRequestLocation | null;
|
|
51
51
|
createdAt: string;
|
|
52
52
|
expiresAt: string;
|
|
53
|
+
/** The platform declared at sign-in (mobile | desktop | web), or null. */
|
|
54
|
+
clientKind: string | null;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
declare const registerSchema: z.ZodUnion<readonly [z.ZodObject<{
|
package/dist/index.js
CHANGED
|
@@ -368,6 +368,79 @@ function validDuration(value, fallback, name) {
|
|
|
368
368
|
return fallback;
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
+
// src/services/session-policy.ts
|
|
372
|
+
var CLIENT_KINDS = ["mobile", "desktop", "web"];
|
|
373
|
+
var SESSION_POLICY_PRESETS = {
|
|
374
|
+
// Personal device; the token lives in the Keychain / Keystore.
|
|
375
|
+
mobile: { sessionDuration: "90d", sessionMaxAge: "365d" },
|
|
376
|
+
// Personal, but often a shared workstation.
|
|
377
|
+
desktop: { sessionDuration: "30d", sessionMaxAge: "180d" },
|
|
378
|
+
// Shared and public computers; cookies.
|
|
379
|
+
web: { sessionDuration: "14d", sessionMaxAge: "90d" }
|
|
380
|
+
};
|
|
381
|
+
function clientKindOf(headers) {
|
|
382
|
+
const v = headers.get("x-client-kind")?.trim().toLowerCase();
|
|
383
|
+
return CLIENT_KINDS.includes(v ?? "") ? v : null;
|
|
384
|
+
}
|
|
385
|
+
var FIELDS = ["sessionDuration", "sessionMaxAge", "accessTokenDuration"];
|
|
386
|
+
function configForClient(config, runtime, kind) {
|
|
387
|
+
const rt = runtime ?? {};
|
|
388
|
+
const code = config;
|
|
389
|
+
const out = { ...config, ...rt };
|
|
390
|
+
for (const field of FIELDS) {
|
|
391
|
+
const value = (kind ? rt.sessionPolicies?.[kind]?.[field] : void 0) ?? rt[field] ?? (kind ? code.sessionPolicies?.[kind]?.[field] : void 0) ?? code[field] ?? (kind ? SESSION_POLICY_PRESETS[kind][field] : void 0);
|
|
392
|
+
if (value === void 0) delete out[field];
|
|
393
|
+
else out[field] = value;
|
|
394
|
+
}
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
function sessionPolicyConfigKeys(kind) {
|
|
398
|
+
return {
|
|
399
|
+
sessionDuration: `auth.session.duration.${kind}`,
|
|
400
|
+
sessionMaxAge: `auth.session.max_age.${kind}`,
|
|
401
|
+
accessTokenDuration: `auth.access.duration.${kind}`
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function readAuthRuntimeConfig(read) {
|
|
405
|
+
const text = (key) => {
|
|
406
|
+
const v = read(key);
|
|
407
|
+
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
408
|
+
};
|
|
409
|
+
const bool2 = (key) => {
|
|
410
|
+
const v = read(key);
|
|
411
|
+
if (typeof v === "boolean") return v;
|
|
412
|
+
if (typeof v === "string" && ["true", "false"].includes(v.trim().toLowerCase())) return v.trim().toLowerCase() === "true";
|
|
413
|
+
return void 0;
|
|
414
|
+
};
|
|
415
|
+
const num = (key) => {
|
|
416
|
+
const v = read(key);
|
|
417
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() ? Number(v) : NaN;
|
|
418
|
+
return Number.isFinite(n) ? n : void 0;
|
|
419
|
+
};
|
|
420
|
+
const out = {};
|
|
421
|
+
const set = (k, v) => {
|
|
422
|
+
if (v !== void 0) out[k] = v;
|
|
423
|
+
};
|
|
424
|
+
set("sessionDuration", text("auth.session.duration"));
|
|
425
|
+
set("sessionMaxAge", text("auth.session.max_age"));
|
|
426
|
+
set("accessTokenDuration", text("auth.access.duration"));
|
|
427
|
+
set("verificationCooldown", num("auth.verification.cooldown"));
|
|
428
|
+
set("mfa", bool2("auth.mfa.enabled"));
|
|
429
|
+
set("requireVerification", bool2("auth.verification.required"));
|
|
430
|
+
const policies = {};
|
|
431
|
+
for (const kind of CLIENT_KINDS) {
|
|
432
|
+
const keys = sessionPolicyConfigKeys(kind);
|
|
433
|
+
const policy = {};
|
|
434
|
+
for (const field of FIELDS) {
|
|
435
|
+
const v = text(keys[field]);
|
|
436
|
+
if (v !== void 0) policy[field] = v;
|
|
437
|
+
}
|
|
438
|
+
if (Object.keys(policy).length) policies[kind] = policy;
|
|
439
|
+
}
|
|
440
|
+
if (Object.keys(policies).length) out.sessionPolicies = policies;
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
|
|
371
444
|
// src/services/mfa.ts
|
|
372
445
|
import { createHmac, randomBytes } from "crypto";
|
|
373
446
|
import { constantTimeEqual } from "@fonderie/core";
|
|
@@ -991,8 +1064,8 @@ var SessionModel = class {
|
|
|
991
1064
|
async create(userId, token, expiresAt, sid, meta) {
|
|
992
1065
|
const location = this.locate && meta?.headers ? await resolveLocation(this.locate, { ip: meta.ipAddress, headers: meta.headers }) : null;
|
|
993
1066
|
await this.store.query(
|
|
994
|
-
`INSERT INTO fonderie_sessions (user_id, token, expires_at, sid, user_agent, ip_address, location, last_used_at)
|
|
995
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, now())
|
|
1067
|
+
`INSERT INTO fonderie_sessions (user_id, token, expires_at, sid, user_agent, ip_address, location, last_used_at, client_kind)
|
|
1068
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, now(), $8)
|
|
996
1069
|
ON CONFLICT (token) DO NOTHING`,
|
|
997
1070
|
[
|
|
998
1071
|
userId,
|
|
@@ -1001,7 +1074,8 @@ var SessionModel = class {
|
|
|
1001
1074
|
sid ?? null,
|
|
1002
1075
|
meta?.userAgent ?? null,
|
|
1003
1076
|
meta?.ipAddress ?? null,
|
|
1004
|
-
location ? JSON.stringify(location) : null
|
|
1077
|
+
location ? JSON.stringify(location) : null,
|
|
1078
|
+
meta?.clientKind ?? null
|
|
1005
1079
|
]
|
|
1006
1080
|
);
|
|
1007
1081
|
}
|
|
@@ -1016,12 +1090,12 @@ var SessionModel = class {
|
|
|
1016
1090
|
async match(token) {
|
|
1017
1091
|
const hash = hashRefreshToken(token);
|
|
1018
1092
|
const [current] = await this.store.query(
|
|
1019
|
-
`SELECT id, user_id, sid, token, created_at FROM fonderie_sessions WHERE token IN ($1, $2) AND expires_at > now() LIMIT 1`,
|
|
1093
|
+
`SELECT id, user_id, sid, token, created_at, client_kind FROM fonderie_sessions WHERE token IN ($1, $2) AND expires_at > now() LIMIT 1`,
|
|
1020
1094
|
[hash, token]
|
|
1021
1095
|
);
|
|
1022
1096
|
if (current) return { kind: "current", row: toRow(current) };
|
|
1023
1097
|
const [previous] = await this.store.query(
|
|
1024
|
-
`SELECT id, user_id, sid, token, created_at, (previous_valid_until > now()) AS in_grace
|
|
1098
|
+
`SELECT id, user_id, sid, token, created_at, client_kind, (previous_valid_until > now()) AS in_grace
|
|
1025
1099
|
FROM fonderie_sessions WHERE previous_token_hash = $1 AND expires_at > now() LIMIT 1`,
|
|
1026
1100
|
[hash]
|
|
1027
1101
|
);
|
|
@@ -1087,7 +1161,7 @@ var SessionModel = class {
|
|
|
1087
1161
|
// No token — that never leaves the server.
|
|
1088
1162
|
async listLiveByUser(userId) {
|
|
1089
1163
|
return this.store.query(
|
|
1090
|
-
`SELECT id, sid,
|
|
1164
|
+
`SELECT id, sid, client_kind AS "clientKind",
|
|
1091
1165
|
user_agent AS "userAgent",
|
|
1092
1166
|
ip_address AS "ipAddress",
|
|
1093
1167
|
location,
|
|
@@ -1136,7 +1210,8 @@ var SessionModel = class {
|
|
|
1136
1210
|
}
|
|
1137
1211
|
};
|
|
1138
1212
|
function toRow(r) {
|
|
1139
|
-
|
|
1213
|
+
const kind = CLIENT_KINDS.includes(r.client_kind ?? "") ? r.client_kind : null;
|
|
1214
|
+
return { id: r.id, userId: r.user_id, sid: r.sid, stored: r.token, createdAt: r.created_at ? new Date(r.created_at) : /* @__PURE__ */ new Date(), clientKind: kind };
|
|
1140
1215
|
}
|
|
1141
1216
|
|
|
1142
1217
|
// src/models/backup-code.model.ts
|
|
@@ -1267,7 +1342,8 @@ function requestMeta(ctx) {
|
|
|
1267
1342
|
const meta = {
|
|
1268
1343
|
ipAddress: typeof ip === "string" && ip.length > 0 ? ip : null,
|
|
1269
1344
|
userAgent: ua && ua.length > 0 ? ua.slice(0, UA_MAX) : null,
|
|
1270
|
-
headers: ctx.request.headers
|
|
1345
|
+
headers: ctx.request.headers,
|
|
1346
|
+
clientKind: clientKindOf(ctx.request.headers)
|
|
1271
1347
|
};
|
|
1272
1348
|
if (meta.ipAddress === null && meta.userAgent === null) warnIfIdentityless(ctx);
|
|
1273
1349
|
return meta;
|
|
@@ -1382,7 +1458,7 @@ function mfaController(store, config, issuer, bus) {
|
|
|
1382
1458
|
await users.enableMfa(ctx.user.id);
|
|
1383
1459
|
}
|
|
1384
1460
|
}
|
|
1385
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, config, {
|
|
1461
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), {
|
|
1386
1462
|
loginMethod: ctx.user.loginMethod,
|
|
1387
1463
|
phoneVerified: ctx.user.phoneVerified
|
|
1388
1464
|
});
|
|
@@ -1744,7 +1820,7 @@ function authController(store, config, bus, locales = defineLocales()) {
|
|
|
1744
1820
|
},
|
|
1745
1821
|
reqOpts
|
|
1746
1822
|
));
|
|
1747
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
|
|
1823
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(user.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), {
|
|
1748
1824
|
loginMethod: "email"
|
|
1749
1825
|
});
|
|
1750
1826
|
const registerMeta = requestMeta(ctx);
|
|
@@ -1887,7 +1963,7 @@ function authController(store, config, bus, locales = defineLocales()) {
|
|
|
1887
1963
|
mfaToken
|
|
1888
1964
|
});
|
|
1889
1965
|
}
|
|
1890
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
|
|
1966
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(user.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), {
|
|
1891
1967
|
loginMethod: "email"
|
|
1892
1968
|
});
|
|
1893
1969
|
await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
|
|
@@ -1980,8 +2056,7 @@ function authController(store, config, bus, locales = defineLocales()) {
|
|
|
1980
2056
|
if (!payload || payload.type !== "refresh") {
|
|
1981
2057
|
return setApiResponse3(HTTP3.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Invalid refresh token");
|
|
1982
2058
|
}
|
|
1983
|
-
const
|
|
1984
|
-
const maxAgeMs = resolvedRefresh.sessionMaxAge ? durationMs(String(resolvedRefresh.sessionMaxAge)) : null;
|
|
2059
|
+
const runtime = config.resolve?.(ctx);
|
|
1985
2060
|
let issued = null;
|
|
1986
2061
|
for (let attempt = 0; attempt < 3 && !issued; attempt++) {
|
|
1987
2062
|
const found = await sessions.match(token);
|
|
@@ -1995,6 +2070,8 @@ function authController(store, config, bus, locales = defineLocales()) {
|
|
|
1995
2070
|
);
|
|
1996
2071
|
return setApiResponse3(HTTP3.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Session expired or already revoked");
|
|
1997
2072
|
}
|
|
2073
|
+
const resolvedRefresh = configForClient(config, runtime, found.row.clientKind);
|
|
2074
|
+
const maxAgeMs = resolvedRefresh.sessionMaxAge ? durationMs(String(resolvedRefresh.sessionMaxAge)) : null;
|
|
1998
2075
|
if (maxAgeMs !== null && Date.now() - found.row.createdAt.getTime() > maxAgeMs) {
|
|
1999
2076
|
await sessions.revokeById(found.row.id);
|
|
2000
2077
|
return setApiResponse3(HTTP3.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Session reached its maximum age \u2014 sign in again");
|
|
@@ -2176,7 +2253,7 @@ function authController(store, config, bus, locales = defineLocales()) {
|
|
|
2176
2253
|
);
|
|
2177
2254
|
}
|
|
2178
2255
|
await phoneVerif.deleteByUser(ctx.user.id);
|
|
2179
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, config, {
|
|
2256
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), {
|
|
2180
2257
|
loginMethod: "phone",
|
|
2181
2258
|
phoneVerified: true
|
|
2182
2259
|
});
|
|
@@ -2353,6 +2430,7 @@ function toSessionDTO(row, currentSid) {
|
|
|
2353
2430
|
ipAddress: row.ipAddress,
|
|
2354
2431
|
userAgent: row.userAgent,
|
|
2355
2432
|
location: sanitizeLocation(row.location),
|
|
2433
|
+
clientKind: row.clientKind ?? null,
|
|
2356
2434
|
createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : String(row.createdAt),
|
|
2357
2435
|
expiresAt: row.expiresAt instanceof Date ? row.expiresAt.toISOString() : String(row.expiresAt)
|
|
2358
2436
|
};
|
|
@@ -2901,7 +2979,7 @@ function oauthController(store, config, bus) {
|
|
|
2901
2979
|
return setApiResponse5(HTTP5.SERVER_ERROR, "SERVER_ERROR", "Apple login failed");
|
|
2902
2980
|
}
|
|
2903
2981
|
await announceOAuthUpsert(ctx, upserted, "apple", fullUser);
|
|
2904
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, config, { loginMethod: "apple" });
|
|
2982
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), { loginMethod: "apple" });
|
|
2905
2983
|
await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
|
|
2906
2984
|
await loginEvents.recordSafe({
|
|
2907
2985
|
userId: upserted.id,
|
|
@@ -3052,7 +3130,7 @@ function oauthController(store, config, bus) {
|
|
|
3052
3130
|
return setApiResponse5(HTTP5.SERVER_ERROR, "SERVER_ERROR", "OAuth login failed");
|
|
3053
3131
|
}
|
|
3054
3132
|
await announceOAuthUpsert(ctx, upserted, "google", fullUser);
|
|
3055
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, config, {
|
|
3133
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), {
|
|
3056
3134
|
loginMethod: "google"
|
|
3057
3135
|
});
|
|
3058
3136
|
await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
|
|
@@ -3640,7 +3718,7 @@ var AuthModule = class {
|
|
|
3640
3718
|
name = "@fonderie/auth";
|
|
3641
3719
|
// Baked in at build time by tsup.base, so the operator's Modules page can
|
|
3642
3720
|
// say what is actually deployed rather than 'not reported'.
|
|
3643
|
-
version = "7.
|
|
3721
|
+
version = "7.18.0";
|
|
3644
3722
|
// Users, sessions, login history, suspend — only through @fonderie/admin.
|
|
3645
3723
|
describeAdmin() {
|
|
3646
3724
|
return { routes: describeAuthAdminRoutes(this.store) };
|
|
@@ -4235,19 +4313,25 @@ function startUserRetention(store, options) {
|
|
|
4235
4313
|
export {
|
|
4236
4314
|
AUTH_CONFIG_KEYS,
|
|
4237
4315
|
AuthModule,
|
|
4316
|
+
CLIENT_KINDS,
|
|
4238
4317
|
DEFAULT_TEMPLATES,
|
|
4239
4318
|
MESSAGE_KEYS,
|
|
4319
|
+
SESSION_POLICY_PRESETS,
|
|
4240
4320
|
buildAuthAccountLimiter,
|
|
4241
4321
|
buildAuthIpLimiter,
|
|
4322
|
+
clientKindOf,
|
|
4323
|
+
configForClient,
|
|
4242
4324
|
describeAuthAdminRoutes,
|
|
4243
4325
|
importUser,
|
|
4244
4326
|
normalizeEmail,
|
|
4245
4327
|
normalizeEmailSafe,
|
|
4246
4328
|
purgeSoftDeletedUsers,
|
|
4329
|
+
readAuthRuntimeConfig,
|
|
4247
4330
|
requireAuth2 as requireAuth,
|
|
4248
4331
|
resolveLocation,
|
|
4249
4332
|
sanitizeLocation,
|
|
4250
4333
|
schemas_exports as schemas,
|
|
4334
|
+
sessionPolicyConfigKeys,
|
|
4251
4335
|
startUserRetention,
|
|
4252
4336
|
toAdminUserDTO,
|
|
4253
4337
|
toAdminUserPageDTO,
|