@fonderie/auth 7.6.0 → 7.7.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.cjs CHANGED
@@ -1048,13 +1048,23 @@ var MAX_HISTORY_LIMIT = 200;
1048
1048
 
1049
1049
  // src/services/request-meta.ts
1050
1050
  var UA_MAX = 512;
1051
+ var warnedAboutMissingIdentity = false;
1052
+ function warnIfIdentityless(ctx) {
1053
+ if (warnedAboutMissingIdentity) return;
1054
+ warnedAboutMissingIdentity = true;
1055
+ console.warn(
1056
+ `[auth] recording a security event with no client IP and no user-agent for ${ctx.request.method} ${new URL(ctx.request.url).pathname}. Login history and session rows will show "Unknown device" with no IP for these. This is what a hand-built request looks like: if you call fonderie.handle() directly, forward the caller's user-agent header and pass the resolved IP as \`handle(req, { meta: { clientIp } })\` \u2014 resolve it with resolveClientIp from @fonderie/core/middlewares rather than reading the socket, or a proxied deployment records the proxy. Reported once per process.`
1057
+ );
1058
+ }
1051
1059
  function requestMeta(ctx) {
1052
1060
  const ip = ctx.meta["clientIp"];
1053
1061
  const ua = ctx.request.headers.get("user-agent");
1054
- return {
1062
+ const meta = {
1055
1063
  ipAddress: typeof ip === "string" && ip.length > 0 ? ip : null,
1056
1064
  userAgent: ua && ua.length > 0 ? ua.slice(0, UA_MAX) : null
1057
1065
  };
1066
+ if (meta.ipAddress === null && meta.userAgent === null) warnIfIdentityless(ctx);
1067
+ return meta;
1058
1068
  }
1059
1069
 
1060
1070
  // src/controllers/mfa.controller.ts
@@ -1900,12 +1910,29 @@ function authController(store, config, bus) {
1900
1910
  }
1901
1911
  const pin = raw.trim();
1902
1912
  if (ctx.user.loginMethod === "phone") {
1913
+ const phoneMeta = requestMeta(ctx);
1903
1914
  const record = await phoneVerif.findByUser(ctx.user.id, pin);
1904
1915
  if (!record) {
1916
+ await loginEvents.recordSafe({
1917
+ userId: ctx.user.id,
1918
+ emailAttempted: null,
1919
+ method: "phone",
1920
+ outcome: "failed",
1921
+ failureReason: "invalid_pin",
1922
+ ...phoneMeta
1923
+ });
1905
1924
  return (0, import_core6.setApiResponse)(import_core6.HTTP.BAD_REQUEST, "VERIFICATION_FAILED", "Invalid or expired pin");
1906
1925
  }
1907
1926
  if (/* @__PURE__ */ new Date() > record.expiresAt) {
1908
1927
  await phoneVerif.deleteByUser(ctx.user.id);
1928
+ await loginEvents.recordSafe({
1929
+ userId: ctx.user.id,
1930
+ emailAttempted: null,
1931
+ method: "phone",
1932
+ outcome: "failed",
1933
+ failureReason: "expired_pin",
1934
+ ...phoneMeta
1935
+ });
1909
1936
  return (0, import_core6.setApiResponse)(
1910
1937
  import_core6.HTTP.BAD_REQUEST,
1911
1938
  "VERIFICATION_FAILED",
@@ -1917,7 +1944,14 @@ function authController(store, config, bus) {
1917
1944
  loginMethod: "phone",
1918
1945
  phoneVerified: true
1919
1946
  });
1920
- await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, requestMeta(ctx));
1947
+ await sessions.create(ctx.user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, phoneMeta);
1948
+ await loginEvents.recordSafe({
1949
+ userId: ctx.user.id,
1950
+ emailAttempted: null,
1951
+ method: "phone",
1952
+ outcome: "success",
1953
+ ...phoneMeta
1954
+ });
1921
1955
  const verifiedUser = await users.findById(ctx.user.id);
1922
1956
  if (!verifiedUser) {
1923
1957
  return (0, import_core6.setApiResponse)(import_core6.HTTP.NOT_FOUND, "NOT_FOUND", "User not found");