@fonderie/auth 7.9.4 → 7.11.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/README.md +31 -0
- package/brain/outcomes.md +2 -0
- package/brain/signatures.md +35 -0
- package/dist/index.cjs +137 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +135 -20
- package/dist/index.js.map +1 -1
- package/dist/middlewares/index.cjs +98 -4
- 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 +98 -4
- package/dist/middlewares/index.js.map +1 -1
- package/dist/migrations/sql/018_login_event_location.sql +6 -0
- package/dist/migrations/sql/019_session_location.sql +5 -0
- package/dist/{session-Bv4qu9xV.d.cts → session-BBF5t-3Y.d.cts} +41 -1
- package/dist/{session-Bv4qu9xV.d.ts → session-BBF5t-3Y.d.ts} +41 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,37 @@ import { withSession, requireAuth } from '@fonderie/auth';
|
|
|
37
37
|
Also exports `toUserDTO`, `normalizeEmail`, and the full type surface
|
|
38
38
|
(`IUser`, `ISession`, `IMfaChallenge`, …).
|
|
39
39
|
|
|
40
|
+
## Where did that request come from? (optional)
|
|
41
|
+
|
|
42
|
+
Pass a `location` resolver and auth stamps a location on every event it
|
|
43
|
+
records — each login attempt, each registration, and each new session:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { geoFromHeaders } from '@fonderie/geo';
|
|
47
|
+
|
|
48
|
+
new AuthModule(store, {
|
|
49
|
+
providers: ['email'],
|
|
50
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
51
|
+
// Vercel/Cloudflare edge headers — zero infrastructure. Trust comes from
|
|
52
|
+
// the deployment, never the request.
|
|
53
|
+
location: ({ headers }) =>
|
|
54
|
+
geoFromHeaders(headers, { trust: process.env.VERCEL ? 'vercel' : undefined }),
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Login history then includes `registration` rows alongside sign-ins, and both
|
|
59
|
+
history events and active sessions carry `location: { country, subdivision,
|
|
60
|
+
city, timeZone, … } | null` (plus `isp`/`asn`/`proxy`/`hosting` if your
|
|
61
|
+
resolver knows them). Registration matters most when verification is not
|
|
62
|
+
enforced: the account is live from that request, so it is the first record of
|
|
63
|
+
where the user came from.
|
|
64
|
+
|
|
65
|
+
Run your migrations: `018_login_event_location.sql` and
|
|
66
|
+
`019_session_location.sql` add the columns. The resolver runs at most once per
|
|
67
|
+
request; its output is sanitized and bounded (coordinates ~1 km); if it throws or takes over 500 ms the row is written without a location
|
|
68
|
+
and the request is unaffected. Country is reliable; region and city are
|
|
69
|
+
approximate.
|
|
70
|
+
|
|
40
71
|
## Why this exists
|
|
41
72
|
|
|
42
73
|
You've shipped this plumbing before — auth, teams, billing, messaging —
|
package/brain/outcomes.md
CHANGED
|
@@ -38,6 +38,7 @@ failure_reason TEXT
|
|
|
38
38
|
ip_address TEXT
|
|
39
39
|
user_agent TEXT
|
|
40
40
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
41
|
+
location JSONB
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
### `fonderie_mfa_backup_codes`
|
|
@@ -93,6 +94,7 @@ ip_address TEXT
|
|
|
93
94
|
expires_at TIMESTAMPTZ NOT NULL
|
|
94
95
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
95
96
|
sid UUID
|
|
97
|
+
location JSONB
|
|
96
98
|
-- INDEX idx_fonderie_sessions_expires_at (expires_at)
|
|
97
99
|
-- INDEX idx_fonderie_sessions_sid (sid)
|
|
98
100
|
```
|
package/brain/signatures.md
CHANGED
|
@@ -56,6 +56,7 @@ new AuthModule(store: IStoreAdapter, config: IAuthConfig, bus?: EventBus | undef
|
|
|
56
56
|
.install(app: IFonderieApp): void
|
|
57
57
|
|
|
58
58
|
interface IAuthConfig extends IAuthSecrets, IAuthRuntimeConfig {
|
|
59
|
+
location?: LocationResolver;
|
|
59
60
|
secureCookies?: boolean;
|
|
60
61
|
rateLimit?: IAuthRateLimitConfig | false;
|
|
61
62
|
accessTokenDuration?: string;
|
|
@@ -139,6 +140,7 @@ interface ILoginEventDTO {
|
|
|
139
140
|
failureReason: string | null;
|
|
140
141
|
ipAddress: string | null;
|
|
141
142
|
userAgent: string | null;
|
|
143
|
+
location: IRequestLocation | null;
|
|
142
144
|
createdAt: string;
|
|
143
145
|
}
|
|
144
146
|
|
|
@@ -152,10 +154,43 @@ interface ISessionDTO {
|
|
|
152
154
|
current: boolean;
|
|
153
155
|
ipAddress: string | null;
|
|
154
156
|
userAgent: string | null;
|
|
157
|
+
location: IRequestLocation | null;
|
|
155
158
|
createdAt: string;
|
|
156
159
|
expiresAt: string;
|
|
157
160
|
}
|
|
158
161
|
|
|
162
|
+
function sanitizeLocation(input: unknown): IRequestLocation | null
|
|
163
|
+
|
|
164
|
+
function resolveLocation(resolver: LocationResolver | undefined, req: ILocationRequest, timeoutMs?: number): Promise<IRequestLocation | null>
|
|
165
|
+
|
|
166
|
+
interface IRequestLocation {
|
|
167
|
+
country?: string | null;
|
|
168
|
+
countryName?: string | null;
|
|
169
|
+
subdivision?: string | null;
|
|
170
|
+
subdivisionName?: string | null;
|
|
171
|
+
city?: string | null;
|
|
172
|
+
postalCode?: string | null;
|
|
173
|
+
continent?: string | null;
|
|
174
|
+
timeZone?: string | null;
|
|
175
|
+
latitude?: number | null;
|
|
176
|
+
longitude?: number | null;
|
|
177
|
+
accuracyRadius?: number | null;
|
|
178
|
+
geonameId?: number | null;
|
|
179
|
+
isp?: string | null;
|
|
180
|
+
org?: string | null;
|
|
181
|
+
asn?: string | null;
|
|
182
|
+
mobile?: boolean | null;
|
|
183
|
+
proxy?: boolean | null;
|
|
184
|
+
hosting?: boolean | null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface ILocationRequest {
|
|
188
|
+
ip: string | null;
|
|
189
|
+
headers: Headers;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
type LocationResolver = (req: ILocationRequest) => IRequestLocation | null | undefined | Promise<IRequestLocation | null | undefined>;
|
|
193
|
+
|
|
159
194
|
function validate(schema: IRequestSchema): Middleware
|
|
160
195
|
|
|
161
196
|
namespace schemas — exports: ChangePasswordInput, LoginInput, RegisterInput, ResetPasswordInput, appleNativeSchema, changePasswordSchema, forgotPasswordSchema, loginSchema, mfaTokenSchema, refreshSchema, registerSchema, resetPasswordSchema, updateEmailSchema, updatePhoneSchema, updatePreferencesSchema, updateProfileSchema, verifySchema
|
package/dist/index.cjs
CHANGED
|
@@ -80,6 +80,8 @@ __export(index_exports, {
|
|
|
80
80
|
normalizeEmailSafe: () => normalizeEmailSafe,
|
|
81
81
|
purgeSoftDeletedUsers: () => purgeSoftDeletedUsers,
|
|
82
82
|
requireAuth: () => import_middlewares3.requireAuth,
|
|
83
|
+
resolveLocation: () => resolveLocation,
|
|
84
|
+
sanitizeLocation: () => sanitizeLocation,
|
|
83
85
|
schemas: () => schemas_exports,
|
|
84
86
|
startUserRetention: () => startUserRetention,
|
|
85
87
|
toAdminUserDTO: () => toAdminUserDTO,
|
|
@@ -881,18 +883,111 @@ var UserModel = class {
|
|
|
881
883
|
};
|
|
882
884
|
var MAX_LIST_LIMIT = 200;
|
|
883
885
|
|
|
886
|
+
// src/services/request-location.ts
|
|
887
|
+
var LOCATION_TIMEOUT_MS = 500;
|
|
888
|
+
var TEXT_MAX = 128;
|
|
889
|
+
function str(v, re) {
|
|
890
|
+
if (typeof v !== "string") return null;
|
|
891
|
+
const t = v.trim();
|
|
892
|
+
if (t.length === 0 || t.length > TEXT_MAX) return null;
|
|
893
|
+
return re && !re.test(t) ? null : t;
|
|
894
|
+
}
|
|
895
|
+
function upperCode(v, re) {
|
|
896
|
+
const t = str(v, re);
|
|
897
|
+
return t ? t.toUpperCase() : null;
|
|
898
|
+
}
|
|
899
|
+
function bool(v) {
|
|
900
|
+
return typeof v === "boolean" ? v : null;
|
|
901
|
+
}
|
|
902
|
+
function coord(v, limit) {
|
|
903
|
+
if (typeof v !== "number" || !Number.isFinite(v) || Math.abs(v) > limit) return null;
|
|
904
|
+
return Math.round(v * 100) / 100;
|
|
905
|
+
}
|
|
906
|
+
function radius(v) {
|
|
907
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v <= 0 || v > 2e4) return null;
|
|
908
|
+
return Math.round(v);
|
|
909
|
+
}
|
|
910
|
+
function placeId(v) {
|
|
911
|
+
const n = typeof v === "string" && /^\d{1,10}$/.test(v) ? Number(v) : v;
|
|
912
|
+
return typeof n === "number" && Number.isInteger(n) && n > 0 && n <= 2147483647 ? n : null;
|
|
913
|
+
}
|
|
914
|
+
function sanitizeLocation(input) {
|
|
915
|
+
if (!input || typeof input !== "object") return null;
|
|
916
|
+
const i = input;
|
|
917
|
+
const out = {
|
|
918
|
+
country: upperCode(i["country"], /^[A-Za-z]{2}$/),
|
|
919
|
+
countryName: str(i["countryName"]),
|
|
920
|
+
subdivision: upperCode(i["subdivision"], /^[A-Za-z0-9]{1,3}$/),
|
|
921
|
+
subdivisionName: str(i["subdivisionName"]),
|
|
922
|
+
city: str(i["city"]),
|
|
923
|
+
postalCode: upperCode(i["postalCode"], /^[A-Za-z0-9][A-Za-z0-9 -]{0,11}$/),
|
|
924
|
+
continent: upperCode(i["continent"], /^[A-Za-z]{2}$/),
|
|
925
|
+
timeZone: str(i["timeZone"], /^(UTC|[A-Za-z_]+(\/[A-Za-z0-9_+-]+)+)$/),
|
|
926
|
+
latitude: coord(i["latitude"], 90),
|
|
927
|
+
longitude: coord(i["longitude"], 180),
|
|
928
|
+
accuracyRadius: radius(i["accuracyRadius"]),
|
|
929
|
+
geonameId: placeId(i["geonameId"]),
|
|
930
|
+
isp: str(i["isp"]),
|
|
931
|
+
org: str(i["org"]),
|
|
932
|
+
asn: upperCode(i["asn"], /^AS\d{1,10}$/i),
|
|
933
|
+
mobile: bool(i["mobile"]),
|
|
934
|
+
proxy: bool(i["proxy"]),
|
|
935
|
+
hosting: bool(i["hosting"])
|
|
936
|
+
};
|
|
937
|
+
const known = Object.fromEntries(Object.entries(out).filter(([, v]) => v !== null));
|
|
938
|
+
return Object.keys(known).length > 0 ? known : null;
|
|
939
|
+
}
|
|
940
|
+
var perRequest = /* @__PURE__ */ new WeakMap();
|
|
941
|
+
function resolveLocation(resolver, req, timeoutMs = LOCATION_TIMEOUT_MS) {
|
|
942
|
+
if (!resolver) return Promise.resolve(null);
|
|
943
|
+
const cached = perRequest.get(req.headers);
|
|
944
|
+
if (cached && cached.resolver === resolver) return cached.result;
|
|
945
|
+
const result = resolveOnce(resolver, req, timeoutMs);
|
|
946
|
+
perRequest.set(req.headers, { resolver, result });
|
|
947
|
+
return result;
|
|
948
|
+
}
|
|
949
|
+
async function resolveOnce(resolver, req, timeoutMs) {
|
|
950
|
+
let timer;
|
|
951
|
+
try {
|
|
952
|
+
const result = await Promise.race([
|
|
953
|
+
Promise.resolve().then(() => resolver(req)),
|
|
954
|
+
new Promise((resolve2) => {
|
|
955
|
+
timer = setTimeout(() => resolve2(null), timeoutMs);
|
|
956
|
+
})
|
|
957
|
+
]);
|
|
958
|
+
return sanitizeLocation(result);
|
|
959
|
+
} catch (err) {
|
|
960
|
+
console.warn("[auth] location resolver failed; recording without a location:", err);
|
|
961
|
+
return null;
|
|
962
|
+
} finally {
|
|
963
|
+
if (timer) clearTimeout(timer);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
884
967
|
// src/models/session.model.ts
|
|
885
968
|
var SessionModel = class {
|
|
886
|
-
|
|
969
|
+
// `locate` is IAuthConfig.location. Absent ⇒ sessions carry no location.
|
|
970
|
+
constructor(store, locate) {
|
|
887
971
|
this.store = store;
|
|
972
|
+
this.locate = locate;
|
|
888
973
|
}
|
|
889
974
|
store;
|
|
975
|
+
locate;
|
|
890
976
|
async create(userId, token, expiresAt, sid, meta) {
|
|
977
|
+
const location = this.locate && meta?.headers ? await resolveLocation(this.locate, { ip: meta.ipAddress, headers: meta.headers }) : null;
|
|
891
978
|
await this.store.query(
|
|
892
|
-
`INSERT INTO fonderie_sessions (user_id, token, expires_at, sid, user_agent, ip_address)
|
|
893
|
-
VALUES ($1, $2, $3, $4, $5, $6)
|
|
979
|
+
`INSERT INTO fonderie_sessions (user_id, token, expires_at, sid, user_agent, ip_address, location)
|
|
980
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
894
981
|
ON CONFLICT (token) DO NOTHING`,
|
|
895
|
-
[
|
|
982
|
+
[
|
|
983
|
+
userId,
|
|
984
|
+
token,
|
|
985
|
+
expiresAt,
|
|
986
|
+
sid ?? null,
|
|
987
|
+
meta?.userAgent ?? null,
|
|
988
|
+
meta?.ipAddress ?? null,
|
|
989
|
+
location ? JSON.stringify(location) : null
|
|
990
|
+
]
|
|
896
991
|
);
|
|
897
992
|
}
|
|
898
993
|
async delete(token) {
|
|
@@ -931,6 +1026,7 @@ var SessionModel = class {
|
|
|
931
1026
|
`SELECT id, sid,
|
|
932
1027
|
user_agent AS "userAgent",
|
|
933
1028
|
ip_address AS "ipAddress",
|
|
1029
|
+
location,
|
|
934
1030
|
created_at AS "createdAt",
|
|
935
1031
|
expires_at AS "expiresAt"
|
|
936
1032
|
FROM fonderie_sessions
|
|
@@ -1014,15 +1110,20 @@ var BackupCodeModel = class {
|
|
|
1014
1110
|
// src/models/login-event.model.ts
|
|
1015
1111
|
var import_core4 = require("@fonderie/core");
|
|
1016
1112
|
var LoginEventModel = class {
|
|
1017
|
-
|
|
1113
|
+
// `locate` is IAuthConfig.location. Absent ⇒ rows carry no location,
|
|
1114
|
+
// exactly as before.
|
|
1115
|
+
constructor(store, locate) {
|
|
1018
1116
|
this.store = store;
|
|
1117
|
+
this.locate = locate;
|
|
1019
1118
|
}
|
|
1020
1119
|
store;
|
|
1120
|
+
locate;
|
|
1021
1121
|
async record(e) {
|
|
1122
|
+
const location = this.locate ? await resolveLocation(this.locate, { ip: e.ipAddress, headers: e.headers ?? new Headers() }) : null;
|
|
1022
1123
|
await this.store.query(
|
|
1023
1124
|
`INSERT INTO fonderie_login_events
|
|
1024
|
-
(user_id, email_attempted, method, outcome, failure_reason, ip_address, user_agent)
|
|
1025
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
1125
|
+
(user_id, email_attempted, method, outcome, failure_reason, ip_address, user_agent, location)
|
|
1126
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
1026
1127
|
[
|
|
1027
1128
|
e.userId,
|
|
1028
1129
|
e.emailAttempted ?? null,
|
|
@@ -1030,7 +1131,8 @@ var LoginEventModel = class {
|
|
|
1030
1131
|
e.outcome,
|
|
1031
1132
|
e.failureReason ?? null,
|
|
1032
1133
|
e.ipAddress,
|
|
1033
|
-
e.userAgent
|
|
1134
|
+
e.userAgent,
|
|
1135
|
+
location ? JSON.stringify(location) : null
|
|
1034
1136
|
]
|
|
1035
1137
|
);
|
|
1036
1138
|
}
|
|
@@ -1069,7 +1171,7 @@ var LoginEventModel = class {
|
|
|
1069
1171
|
params.push(limit + 1);
|
|
1070
1172
|
const rows = await this.store.query(
|
|
1071
1173
|
`SELECT id, method, outcome, failure_reason AS "failureReason",
|
|
1072
|
-
ip_address AS "ipAddress", user_agent AS "userAgent",
|
|
1174
|
+
ip_address AS "ipAddress", user_agent AS "userAgent", location,
|
|
1073
1175
|
created_at AS "createdAt", created_at::text AS "createdAtRaw"
|
|
1074
1176
|
FROM fonderie_login_events
|
|
1075
1177
|
WHERE ${where.join(" AND ")}
|
|
@@ -1097,7 +1199,8 @@ function requestMeta(ctx) {
|
|
|
1097
1199
|
const ua = ctx.request.headers.get("user-agent");
|
|
1098
1200
|
const meta = {
|
|
1099
1201
|
ipAddress: typeof ip === "string" && ip.length > 0 ? ip : null,
|
|
1100
|
-
userAgent: ua && ua.length > 0 ? ua.slice(0, UA_MAX) : null
|
|
1202
|
+
userAgent: ua && ua.length > 0 ? ua.slice(0, UA_MAX) : null,
|
|
1203
|
+
headers: ctx.request.headers
|
|
1101
1204
|
};
|
|
1102
1205
|
if (meta.ipAddress === null && meta.userAgent === null) warnIfIdentityless(ctx);
|
|
1103
1206
|
return meta;
|
|
@@ -1106,9 +1209,9 @@ function requestMeta(ctx) {
|
|
|
1106
1209
|
// src/controllers/mfa.controller.ts
|
|
1107
1210
|
function mfaController(store, config, issuer, bus) {
|
|
1108
1211
|
const users = new UserModel(store);
|
|
1109
|
-
const sessions = new SessionModel(store);
|
|
1212
|
+
const sessions = new SessionModel(store, config.location);
|
|
1110
1213
|
const backupCodes = new BackupCodeModel(store);
|
|
1111
|
-
const loginEvents = new LoginEventModel(store);
|
|
1214
|
+
const loginEvents = new LoginEventModel(store, config.location);
|
|
1112
1215
|
const mfaCipher = makeMfaCipher(config.mfaSecretKey);
|
|
1113
1216
|
return {
|
|
1114
1217
|
// ── 1. Setup ───────────────────────────────────────────────
|
|
@@ -1506,8 +1609,8 @@ function extractRefreshToken(ctx) {
|
|
|
1506
1609
|
}
|
|
1507
1610
|
function authController(store, config, bus) {
|
|
1508
1611
|
const users = new UserModel(store);
|
|
1509
|
-
const sessions = new SessionModel(store);
|
|
1510
|
-
const loginEvents = new LoginEventModel(store);
|
|
1612
|
+
const sessions = new SessionModel(store, config.location);
|
|
1613
|
+
const loginEvents = new LoginEventModel(store, config.location);
|
|
1511
1614
|
const passwordReset = new PasswordResetModel(store);
|
|
1512
1615
|
const emailVerif = new EmailVerificationModel(store);
|
|
1513
1616
|
const phoneVerif = new PhoneVerificationModel(store);
|
|
@@ -1575,7 +1678,15 @@ function authController(store, config, bus) {
|
|
|
1575
1678
|
const { accessToken, refreshToken, sid } = issueTokenPair(user.id, config, {
|
|
1576
1679
|
loginMethod: "email"
|
|
1577
1680
|
});
|
|
1578
|
-
|
|
1681
|
+
const registerMeta = requestMeta(ctx);
|
|
1682
|
+
await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, registerMeta);
|
|
1683
|
+
await loginEvents.recordSafe({
|
|
1684
|
+
userId: user.id,
|
|
1685
|
+
emailAttempted: normalizedEmail,
|
|
1686
|
+
method: "registration",
|
|
1687
|
+
outcome: "success",
|
|
1688
|
+
...registerMeta
|
|
1689
|
+
});
|
|
1579
1690
|
const resolvedRegister = { ...config, ...config.resolve?.(ctx) };
|
|
1580
1691
|
const requiresVerification = !!resolvedRegister.requireVerification && !user.emailVerifiedAt;
|
|
1581
1692
|
return Response.json(
|
|
@@ -2134,6 +2245,9 @@ function toLoginEventDTO(row) {
|
|
|
2134
2245
|
failureReason: row.failureReason,
|
|
2135
2246
|
ipAddress: row.ipAddress,
|
|
2136
2247
|
userAgent: row.userAgent,
|
|
2248
|
+
// Re-sanitized on read: the column is JSONB and could have been written
|
|
2249
|
+
// by anything with database access.
|
|
2250
|
+
location: sanitizeLocation(row.location),
|
|
2137
2251
|
createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : String(row.createdAt)
|
|
2138
2252
|
};
|
|
2139
2253
|
}
|
|
@@ -2149,6 +2263,7 @@ function toSessionDTO(row, currentSid) {
|
|
|
2149
2263
|
current: currentSid !== null && row.sid === currentSid,
|
|
2150
2264
|
ipAddress: row.ipAddress,
|
|
2151
2265
|
userAgent: row.userAgent,
|
|
2266
|
+
location: sanitizeLocation(row.location),
|
|
2152
2267
|
createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : String(row.createdAt),
|
|
2153
2268
|
expiresAt: row.expiresAt instanceof Date ? row.expiresAt.toISOString() : String(row.expiresAt)
|
|
2154
2269
|
};
|
|
@@ -2163,8 +2278,8 @@ function isValidPhone2(phone2) {
|
|
|
2163
2278
|
}
|
|
2164
2279
|
function userController(store, config, bus) {
|
|
2165
2280
|
const users = new UserModel(store);
|
|
2166
|
-
const sessions = new SessionModel(store);
|
|
2167
|
-
const loginEvents = new LoginEventModel(store);
|
|
2281
|
+
const sessions = new SessionModel(store, config.location);
|
|
2282
|
+
const loginEvents = new LoginEventModel(store, config.location);
|
|
2168
2283
|
const emailVerif = new EmailVerificationModel(store);
|
|
2169
2284
|
const phoneVerif = new PhoneVerificationModel(store);
|
|
2170
2285
|
return {
|
|
@@ -2608,8 +2723,8 @@ async function verifyAppleIdToken(idToken, opts) {
|
|
|
2608
2723
|
var appleEmailVerified = (c) => c.email_verified === true || c.email_verified === "true";
|
|
2609
2724
|
function oauthController(store, config, bus) {
|
|
2610
2725
|
const users = new UserModel(store);
|
|
2611
|
-
const sessions = new SessionModel(store);
|
|
2612
|
-
const loginEvents = new LoginEventModel(store);
|
|
2726
|
+
const sessions = new SessionModel(store, config.location);
|
|
2727
|
+
const loginEvents = new LoginEventModel(store, config.location);
|
|
2613
2728
|
const consumedTokens = new ConsumedTokenModel(store);
|
|
2614
2729
|
const announceOAuthUpsert = async (ctx, upserted, provider, user) => {
|
|
2615
2730
|
if (!bus) return;
|
|
@@ -3398,7 +3513,7 @@ var AuthModule = class {
|
|
|
3398
3513
|
name = "@fonderie/auth";
|
|
3399
3514
|
// Baked in at build time by tsup.base, so the operator's Modules page can
|
|
3400
3515
|
// say what is actually deployed rather than 'not reported'.
|
|
3401
|
-
version = "7.
|
|
3516
|
+
version = "7.11.0";
|
|
3402
3517
|
// Users, sessions, login history, suspend — only through @fonderie/admin.
|
|
3403
3518
|
describeAdmin() {
|
|
3404
3519
|
return { routes: describeAuthAdminRoutes(this.store) };
|
|
@@ -3676,6 +3791,8 @@ function startUserRetention(store, options) {
|
|
|
3676
3791
|
normalizeEmailSafe,
|
|
3677
3792
|
purgeSoftDeletedUsers,
|
|
3678
3793
|
requireAuth,
|
|
3794
|
+
resolveLocation,
|
|
3795
|
+
sanitizeLocation,
|
|
3679
3796
|
schemas,
|
|
3680
3797
|
startUserRetention,
|
|
3681
3798
|
toAdminUserDTO,
|