@fonderie/auth 7.17.0 → 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 +120 -19
- 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 +114 -19
- 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/brain/outcomes.md
CHANGED
package/brain/signatures.md
CHANGED
|
@@ -93,6 +93,7 @@ interface IAuthSecrets {
|
|
|
93
93
|
interface IAuthRuntimeConfig {
|
|
94
94
|
sessionDuration?: string;
|
|
95
95
|
sessionMaxAge?: string;
|
|
96
|
+
sessionPolicies?: Partial<Record<ClientKind, ISessionPolicy>>;
|
|
96
97
|
verificationCooldown?: number;
|
|
97
98
|
mfa?: boolean;
|
|
98
99
|
requireVerification?: boolean;
|
|
@@ -103,7 +104,7 @@ interface IDataExportContributor {
|
|
|
103
104
|
collect: (userId: string) => Promise<unknown> | unknown;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
const AUTH_CONFIG_KEYS: Record<keyof IAuthRuntimeConfig, string>
|
|
107
|
+
const AUTH_CONFIG_KEYS: Record<Exclude<keyof IAuthRuntimeConfig, 'sessionPolicies'>, string>
|
|
107
108
|
|
|
108
109
|
const MESSAGE_KEYS: { readonly emailRegistration: "email-registration"; readonly emailVerification: "email-verification"; readonly passwordReset: "password-reset"; readonly phoneOtp: "phone-otp"; readonly mfaEnabled: "mfa-enabled"; readonly mfaDisabled: "mfa-disabled"; readonly mfaBackupCodesRegenerated: "mfa-backup-codes-regenerated"; readonly emailChanged: "email-changed"; readonly phoneChanged: "phone-changed"; readonly passwordRevoked: "password-revoked"; readonly oauthRegistration: "oauth-registration"; readonly oauthLinked: "oauth-linked"; readonly oauthUnlinked: "oauth-unlinked"; }
|
|
109
110
|
|
|
@@ -159,6 +160,7 @@ interface ISessionDTO {
|
|
|
159
160
|
location: IRequestLocation | null;
|
|
160
161
|
createdAt: string;
|
|
161
162
|
expiresAt: string;
|
|
163
|
+
clientKind: string | null;
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
function sanitizeLocation(input: unknown): IRequestLocation | null
|
|
@@ -270,4 +272,24 @@ function describeAuthAdminRoutes(store: IStoreAdapter): IAdminRoute[]
|
|
|
270
272
|
function toAdminUserDTO(user: IUser): IAdminUserDTO
|
|
271
273
|
|
|
272
274
|
function toAdminUserPageDTO(page: IUserPage): IAdminUserPageDTO
|
|
275
|
+
|
|
276
|
+
const CLIENT_KINDS: readonly ["mobile", "desktop", "web"]
|
|
277
|
+
|
|
278
|
+
const SESSION_POLICY_PRESETS: Readonly<Record<ClientKind, ISessionPolicy>>
|
|
279
|
+
|
|
280
|
+
function clientKindOf(headers: Headers): "mobile" | "desktop" | "web" | null
|
|
281
|
+
|
|
282
|
+
function configForClient(config: IAuthConfig, runtime: Partial<IAuthRuntimeConfig> | undefined, kind: "mobile" | "desktop" | "web" | null): IAuthConfig
|
|
283
|
+
|
|
284
|
+
function readAuthRuntimeConfig(read: (key: string) => unknown): Partial<IAuthRuntimeConfig>
|
|
285
|
+
|
|
286
|
+
function sessionPolicyConfigKeys(kind: "mobile" | "desktop" | "web"): Record<keyof ISessionPolicy, string>
|
|
287
|
+
|
|
288
|
+
type ClientKind = (typeof CLIENT_KINDS)[number];
|
|
289
|
+
|
|
290
|
+
interface ISessionPolicy {
|
|
291
|
+
sessionDuration?: string;
|
|
292
|
+
sessionMaxAge?: string;
|
|
293
|
+
accessTokenDuration?: string;
|
|
294
|
+
}
|
|
273
295
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -70,19 +70,25 @@ var index_exports = {};
|
|
|
70
70
|
__export(index_exports, {
|
|
71
71
|
AUTH_CONFIG_KEYS: () => AUTH_CONFIG_KEYS,
|
|
72
72
|
AuthModule: () => AuthModule,
|
|
73
|
+
CLIENT_KINDS: () => CLIENT_KINDS,
|
|
73
74
|
DEFAULT_TEMPLATES: () => DEFAULT_TEMPLATES,
|
|
74
75
|
MESSAGE_KEYS: () => MESSAGE_KEYS,
|
|
76
|
+
SESSION_POLICY_PRESETS: () => SESSION_POLICY_PRESETS,
|
|
75
77
|
buildAuthAccountLimiter: () => buildAuthAccountLimiter,
|
|
76
78
|
buildAuthIpLimiter: () => buildAuthIpLimiter,
|
|
79
|
+
clientKindOf: () => clientKindOf,
|
|
80
|
+
configForClient: () => configForClient,
|
|
77
81
|
describeAuthAdminRoutes: () => describeAuthAdminRoutes,
|
|
78
82
|
importUser: () => importUser,
|
|
79
83
|
normalizeEmail: () => normalizeEmail,
|
|
80
84
|
normalizeEmailSafe: () => normalizeEmailSafe,
|
|
81
85
|
purgeSoftDeletedUsers: () => purgeSoftDeletedUsers,
|
|
86
|
+
readAuthRuntimeConfig: () => readAuthRuntimeConfig,
|
|
82
87
|
requireAuth: () => import_middlewares3.requireAuth,
|
|
83
88
|
resolveLocation: () => resolveLocation,
|
|
84
89
|
sanitizeLocation: () => sanitizeLocation,
|
|
85
90
|
schemas: () => schemas_exports,
|
|
91
|
+
sessionPolicyConfigKeys: () => sessionPolicyConfigKeys,
|
|
86
92
|
startUserRetention: () => startUserRetention,
|
|
87
93
|
toAdminUserDTO: () => toAdminUserDTO,
|
|
88
94
|
toAdminUserPageDTO: () => toAdminUserPageDTO,
|
|
@@ -357,8 +363,8 @@ function issueMfaPendingToken(userId, config, loginMethod) {
|
|
|
357
363
|
);
|
|
358
364
|
}
|
|
359
365
|
function issueTokenPair(userId, config, options) {
|
|
360
|
-
const duration = config.sessionDuration
|
|
361
|
-
const accessDuration = config.accessTokenDuration
|
|
366
|
+
const duration = validDuration(config.sessionDuration, DEFAULT_SESSION_DURATION, "sessionDuration");
|
|
367
|
+
const accessDuration = validDuration(config.accessTokenDuration, DEFAULT_ACCESS_TOKEN_DURATION, "accessTokenDuration");
|
|
362
368
|
const authTime = options.authTime ?? Math.floor(Date.now() / 1e3);
|
|
363
369
|
const loginMethod = options.loginMethod;
|
|
364
370
|
const phoneVerified = options.phoneVerified ?? false;
|
|
@@ -401,6 +407,90 @@ function durationMs(value) {
|
|
|
401
407
|
const scale = { ms: 1, s: 1e3, m: 6e4, h: 36e5, d: 864e5, w: 6048e5, y: 315576e5 };
|
|
402
408
|
return Number(m[1]) * (scale[unit] ?? 1e3);
|
|
403
409
|
}
|
|
410
|
+
var warnedDurations = /* @__PURE__ */ new Set();
|
|
411
|
+
function validDuration(value, fallback, name) {
|
|
412
|
+
if (value === void 0 || value === null || value === "") return fallback;
|
|
413
|
+
if (durationMs(String(value)) !== null) return String(value);
|
|
414
|
+
const key = `${name}=${String(value)}`;
|
|
415
|
+
if (!warnedDurations.has(key)) {
|
|
416
|
+
warnedDurations.add(key);
|
|
417
|
+
console.warn(`[auth] ${name} ${JSON.stringify(value)} is not a duration (e.g. '90d', '1h') \u2014 using ${fallback}`);
|
|
418
|
+
}
|
|
419
|
+
return fallback;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/services/session-policy.ts
|
|
423
|
+
var CLIENT_KINDS = ["mobile", "desktop", "web"];
|
|
424
|
+
var SESSION_POLICY_PRESETS = {
|
|
425
|
+
// Personal device; the token lives in the Keychain / Keystore.
|
|
426
|
+
mobile: { sessionDuration: "90d", sessionMaxAge: "365d" },
|
|
427
|
+
// Personal, but often a shared workstation.
|
|
428
|
+
desktop: { sessionDuration: "30d", sessionMaxAge: "180d" },
|
|
429
|
+
// Shared and public computers; cookies.
|
|
430
|
+
web: { sessionDuration: "14d", sessionMaxAge: "90d" }
|
|
431
|
+
};
|
|
432
|
+
function clientKindOf(headers) {
|
|
433
|
+
const v = headers.get("x-client-kind")?.trim().toLowerCase();
|
|
434
|
+
return CLIENT_KINDS.includes(v ?? "") ? v : null;
|
|
435
|
+
}
|
|
436
|
+
var FIELDS = ["sessionDuration", "sessionMaxAge", "accessTokenDuration"];
|
|
437
|
+
function configForClient(config, runtime, kind) {
|
|
438
|
+
const rt = runtime ?? {};
|
|
439
|
+
const code = config;
|
|
440
|
+
const out = { ...config, ...rt };
|
|
441
|
+
for (const field of FIELDS) {
|
|
442
|
+
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);
|
|
443
|
+
if (value === void 0) delete out[field];
|
|
444
|
+
else out[field] = value;
|
|
445
|
+
}
|
|
446
|
+
return out;
|
|
447
|
+
}
|
|
448
|
+
function sessionPolicyConfigKeys(kind) {
|
|
449
|
+
return {
|
|
450
|
+
sessionDuration: `auth.session.duration.${kind}`,
|
|
451
|
+
sessionMaxAge: `auth.session.max_age.${kind}`,
|
|
452
|
+
accessTokenDuration: `auth.access.duration.${kind}`
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function readAuthRuntimeConfig(read) {
|
|
456
|
+
const text = (key) => {
|
|
457
|
+
const v = read(key);
|
|
458
|
+
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
459
|
+
};
|
|
460
|
+
const bool2 = (key) => {
|
|
461
|
+
const v = read(key);
|
|
462
|
+
if (typeof v === "boolean") return v;
|
|
463
|
+
if (typeof v === "string" && ["true", "false"].includes(v.trim().toLowerCase())) return v.trim().toLowerCase() === "true";
|
|
464
|
+
return void 0;
|
|
465
|
+
};
|
|
466
|
+
const num = (key) => {
|
|
467
|
+
const v = read(key);
|
|
468
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() ? Number(v) : NaN;
|
|
469
|
+
return Number.isFinite(n) ? n : void 0;
|
|
470
|
+
};
|
|
471
|
+
const out = {};
|
|
472
|
+
const set = (k, v) => {
|
|
473
|
+
if (v !== void 0) out[k] = v;
|
|
474
|
+
};
|
|
475
|
+
set("sessionDuration", text("auth.session.duration"));
|
|
476
|
+
set("sessionMaxAge", text("auth.session.max_age"));
|
|
477
|
+
set("accessTokenDuration", text("auth.access.duration"));
|
|
478
|
+
set("verificationCooldown", num("auth.verification.cooldown"));
|
|
479
|
+
set("mfa", bool2("auth.mfa.enabled"));
|
|
480
|
+
set("requireVerification", bool2("auth.verification.required"));
|
|
481
|
+
const policies = {};
|
|
482
|
+
for (const kind of CLIENT_KINDS) {
|
|
483
|
+
const keys = sessionPolicyConfigKeys(kind);
|
|
484
|
+
const policy = {};
|
|
485
|
+
for (const field of FIELDS) {
|
|
486
|
+
const v = text(keys[field]);
|
|
487
|
+
if (v !== void 0) policy[field] = v;
|
|
488
|
+
}
|
|
489
|
+
if (Object.keys(policy).length) policies[kind] = policy;
|
|
490
|
+
}
|
|
491
|
+
if (Object.keys(policies).length) out.sessionPolicies = policies;
|
|
492
|
+
return out;
|
|
493
|
+
}
|
|
404
494
|
|
|
405
495
|
// src/services/mfa.ts
|
|
406
496
|
var import_node_crypto2 = require("crypto");
|
|
@@ -1025,8 +1115,8 @@ var SessionModel = class {
|
|
|
1025
1115
|
async create(userId, token, expiresAt, sid, meta) {
|
|
1026
1116
|
const location = this.locate && meta?.headers ? await resolveLocation(this.locate, { ip: meta.ipAddress, headers: meta.headers }) : null;
|
|
1027
1117
|
await this.store.query(
|
|
1028
|
-
`INSERT INTO fonderie_sessions (user_id, token, expires_at, sid, user_agent, ip_address, location, last_used_at)
|
|
1029
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, now())
|
|
1118
|
+
`INSERT INTO fonderie_sessions (user_id, token, expires_at, sid, user_agent, ip_address, location, last_used_at, client_kind)
|
|
1119
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, now(), $8)
|
|
1030
1120
|
ON CONFLICT (token) DO NOTHING`,
|
|
1031
1121
|
[
|
|
1032
1122
|
userId,
|
|
@@ -1035,7 +1125,8 @@ var SessionModel = class {
|
|
|
1035
1125
|
sid ?? null,
|
|
1036
1126
|
meta?.userAgent ?? null,
|
|
1037
1127
|
meta?.ipAddress ?? null,
|
|
1038
|
-
location ? JSON.stringify(location) : null
|
|
1128
|
+
location ? JSON.stringify(location) : null,
|
|
1129
|
+
meta?.clientKind ?? null
|
|
1039
1130
|
]
|
|
1040
1131
|
);
|
|
1041
1132
|
}
|
|
@@ -1050,12 +1141,12 @@ var SessionModel = class {
|
|
|
1050
1141
|
async match(token) {
|
|
1051
1142
|
const hash = hashRefreshToken(token);
|
|
1052
1143
|
const [current] = await this.store.query(
|
|
1053
|
-
`SELECT id, user_id, sid, token, created_at FROM fonderie_sessions WHERE token IN ($1, $2) AND expires_at > now() LIMIT 1`,
|
|
1144
|
+
`SELECT id, user_id, sid, token, created_at, client_kind FROM fonderie_sessions WHERE token IN ($1, $2) AND expires_at > now() LIMIT 1`,
|
|
1054
1145
|
[hash, token]
|
|
1055
1146
|
);
|
|
1056
1147
|
if (current) return { kind: "current", row: toRow(current) };
|
|
1057
1148
|
const [previous] = await this.store.query(
|
|
1058
|
-
`SELECT id, user_id, sid, token, created_at, (previous_valid_until > now()) AS in_grace
|
|
1149
|
+
`SELECT id, user_id, sid, token, created_at, client_kind, (previous_valid_until > now()) AS in_grace
|
|
1059
1150
|
FROM fonderie_sessions WHERE previous_token_hash = $1 AND expires_at > now() LIMIT 1`,
|
|
1060
1151
|
[hash]
|
|
1061
1152
|
);
|
|
@@ -1121,7 +1212,7 @@ var SessionModel = class {
|
|
|
1121
1212
|
// No token — that never leaves the server.
|
|
1122
1213
|
async listLiveByUser(userId) {
|
|
1123
1214
|
return this.store.query(
|
|
1124
|
-
`SELECT id, sid,
|
|
1215
|
+
`SELECT id, sid, client_kind AS "clientKind",
|
|
1125
1216
|
user_agent AS "userAgent",
|
|
1126
1217
|
ip_address AS "ipAddress",
|
|
1127
1218
|
location,
|
|
@@ -1170,7 +1261,8 @@ var SessionModel = class {
|
|
|
1170
1261
|
}
|
|
1171
1262
|
};
|
|
1172
1263
|
function toRow(r) {
|
|
1173
|
-
|
|
1264
|
+
const kind = CLIENT_KINDS.includes(r.client_kind ?? "") ? r.client_kind : null;
|
|
1265
|
+
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 };
|
|
1174
1266
|
}
|
|
1175
1267
|
|
|
1176
1268
|
// src/models/backup-code.model.ts
|
|
@@ -1301,7 +1393,8 @@ function requestMeta(ctx) {
|
|
|
1301
1393
|
const meta = {
|
|
1302
1394
|
ipAddress: typeof ip === "string" && ip.length > 0 ? ip : null,
|
|
1303
1395
|
userAgent: ua && ua.length > 0 ? ua.slice(0, UA_MAX) : null,
|
|
1304
|
-
headers: ctx.request.headers
|
|
1396
|
+
headers: ctx.request.headers,
|
|
1397
|
+
clientKind: clientKindOf(ctx.request.headers)
|
|
1305
1398
|
};
|
|
1306
1399
|
if (meta.ipAddress === null && meta.userAgent === null) warnIfIdentityless(ctx);
|
|
1307
1400
|
return meta;
|
|
@@ -1416,7 +1509,7 @@ function mfaController(store, config, issuer, bus) {
|
|
|
1416
1509
|
await users.enableMfa(ctx.user.id);
|
|
1417
1510
|
}
|
|
1418
1511
|
}
|
|
1419
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, config, {
|
|
1512
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), {
|
|
1420
1513
|
loginMethod: ctx.user.loginMethod,
|
|
1421
1514
|
phoneVerified: ctx.user.phoneVerified
|
|
1422
1515
|
});
|
|
@@ -1778,7 +1871,7 @@ function authController(store, config, bus, locales = (0, import_core6.defineLoc
|
|
|
1778
1871
|
},
|
|
1779
1872
|
reqOpts
|
|
1780
1873
|
));
|
|
1781
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
|
|
1874
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(user.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), {
|
|
1782
1875
|
loginMethod: "email"
|
|
1783
1876
|
});
|
|
1784
1877
|
const registerMeta = requestMeta(ctx);
|
|
@@ -1921,7 +2014,7 @@ function authController(store, config, bus, locales = (0, import_core6.defineLoc
|
|
|
1921
2014
|
mfaToken
|
|
1922
2015
|
});
|
|
1923
2016
|
}
|
|
1924
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
|
|
2017
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(user.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), {
|
|
1925
2018
|
loginMethod: "email"
|
|
1926
2019
|
});
|
|
1927
2020
|
await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
|
|
@@ -2014,8 +2107,7 @@ function authController(store, config, bus, locales = (0, import_core6.defineLoc
|
|
|
2014
2107
|
if (!payload || payload.type !== "refresh") {
|
|
2015
2108
|
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Invalid refresh token");
|
|
2016
2109
|
}
|
|
2017
|
-
const
|
|
2018
|
-
const maxAgeMs = durationMs(resolvedRefresh.sessionMaxAge);
|
|
2110
|
+
const runtime = config.resolve?.(ctx);
|
|
2019
2111
|
let issued = null;
|
|
2020
2112
|
for (let attempt = 0; attempt < 3 && !issued; attempt++) {
|
|
2021
2113
|
const found = await sessions.match(token);
|
|
@@ -2029,6 +2121,8 @@ function authController(store, config, bus, locales = (0, import_core6.defineLoc
|
|
|
2029
2121
|
);
|
|
2030
2122
|
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Session expired or already revoked");
|
|
2031
2123
|
}
|
|
2124
|
+
const resolvedRefresh = configForClient(config, runtime, found.row.clientKind);
|
|
2125
|
+
const maxAgeMs = resolvedRefresh.sessionMaxAge ? durationMs(String(resolvedRefresh.sessionMaxAge)) : null;
|
|
2032
2126
|
if (maxAgeMs !== null && Date.now() - found.row.createdAt.getTime() > maxAgeMs) {
|
|
2033
2127
|
await sessions.revokeById(found.row.id);
|
|
2034
2128
|
return (0, import_core6.setApiResponse)(import_core6.HTTP.UNAUTHORIZED, "TOKEN_REFRESH_FAILED", "Session reached its maximum age \u2014 sign in again");
|
|
@@ -2210,7 +2304,7 @@ function authController(store, config, bus, locales = (0, import_core6.defineLoc
|
|
|
2210
2304
|
);
|
|
2211
2305
|
}
|
|
2212
2306
|
await phoneVerif.deleteByUser(ctx.user.id);
|
|
2213
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, config, {
|
|
2307
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(ctx.user.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), {
|
|
2214
2308
|
loginMethod: "phone",
|
|
2215
2309
|
phoneVerified: true
|
|
2216
2310
|
});
|
|
@@ -2387,6 +2481,7 @@ function toSessionDTO(row, currentSid) {
|
|
|
2387
2481
|
ipAddress: row.ipAddress,
|
|
2388
2482
|
userAgent: row.userAgent,
|
|
2389
2483
|
location: sanitizeLocation(row.location),
|
|
2484
|
+
clientKind: row.clientKind ?? null,
|
|
2390
2485
|
createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : String(row.createdAt),
|
|
2391
2486
|
expiresAt: row.expiresAt instanceof Date ? row.expiresAt.toISOString() : String(row.expiresAt)
|
|
2392
2487
|
};
|
|
@@ -2935,7 +3030,7 @@ function oauthController(store, config, bus) {
|
|
|
2935
3030
|
return (0, import_core9.setApiResponse)(import_core9.HTTP.SERVER_ERROR, "SERVER_ERROR", "Apple login failed");
|
|
2936
3031
|
}
|
|
2937
3032
|
await announceOAuthUpsert(ctx, upserted, "apple", fullUser);
|
|
2938
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, config, { loginMethod: "apple" });
|
|
3033
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), { loginMethod: "apple" });
|
|
2939
3034
|
await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
|
|
2940
3035
|
await loginEvents.recordSafe({
|
|
2941
3036
|
userId: upserted.id,
|
|
@@ -3086,7 +3181,7 @@ function oauthController(store, config, bus) {
|
|
|
3086
3181
|
return (0, import_core9.setApiResponse)(import_core9.HTTP.SERVER_ERROR, "SERVER_ERROR", "OAuth login failed");
|
|
3087
3182
|
}
|
|
3088
3183
|
await announceOAuthUpsert(ctx, upserted, "google", fullUser);
|
|
3089
|
-
const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, config, {
|
|
3184
|
+
const { accessToken, refreshToken, sid } = issueTokenPair(upserted.id, configForClient(config, config.resolve?.(ctx), clientKindOf(ctx.request.headers)), {
|
|
3090
3185
|
loginMethod: "google"
|
|
3091
3186
|
});
|
|
3092
3187
|
await sessions.create(upserted.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
|
|
@@ -3674,7 +3769,7 @@ var AuthModule = class {
|
|
|
3674
3769
|
name = "@fonderie/auth";
|
|
3675
3770
|
// Baked in at build time by tsup.base, so the operator's Modules page can
|
|
3676
3771
|
// say what is actually deployed rather than 'not reported'.
|
|
3677
|
-
version = "7.
|
|
3772
|
+
version = "7.18.0";
|
|
3678
3773
|
// Users, sessions, login history, suspend — only through @fonderie/admin.
|
|
3679
3774
|
describeAdmin() {
|
|
3680
3775
|
return { routes: describeAuthAdminRoutes(this.store) };
|
|
@@ -4270,19 +4365,25 @@ function startUserRetention(store, options) {
|
|
|
4270
4365
|
0 && (module.exports = {
|
|
4271
4366
|
AUTH_CONFIG_KEYS,
|
|
4272
4367
|
AuthModule,
|
|
4368
|
+
CLIENT_KINDS,
|
|
4273
4369
|
DEFAULT_TEMPLATES,
|
|
4274
4370
|
MESSAGE_KEYS,
|
|
4371
|
+
SESSION_POLICY_PRESETS,
|
|
4275
4372
|
buildAuthAccountLimiter,
|
|
4276
4373
|
buildAuthIpLimiter,
|
|
4374
|
+
clientKindOf,
|
|
4375
|
+
configForClient,
|
|
4277
4376
|
describeAuthAdminRoutes,
|
|
4278
4377
|
importUser,
|
|
4279
4378
|
normalizeEmail,
|
|
4280
4379
|
normalizeEmailSafe,
|
|
4281
4380
|
purgeSoftDeletedUsers,
|
|
4381
|
+
readAuthRuntimeConfig,
|
|
4282
4382
|
requireAuth,
|
|
4283
4383
|
resolveLocation,
|
|
4284
4384
|
sanitizeLocation,
|
|
4285
4385
|
schemas,
|
|
4386
|
+
sessionPolicyConfigKeys,
|
|
4286
4387
|
startUserRetention,
|
|
4287
4388
|
toAdminUserDTO,
|
|
4288
4389
|
toAdminUserPageDTO,
|