@fonderie/auth 7.0.1 → 7.1.1

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.cts CHANGED
@@ -2,8 +2,8 @@ export { IMfaChallenge, ISession, IUser } from './types.cjs';
2
2
  import { IFonderieModule, IReadinessProblem, IFonderieApp } from '@fonderie/core';
3
3
  import { IStoreAdapter } from '@fonderie/store';
4
4
  import { EventBus } from '@fonderie/events';
5
- import { I as IAuthConfig } from './session-Db6KZca3.cjs';
6
- export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, f as IDataExportContributor, M as MESSAGE_KEYS, g as buildAuthAccountLimiter, h as buildAuthIpLimiter, w as withSession } from './session-Db6KZca3.cjs';
5
+ import { I as IAuthConfig } from './session-6B77MAFn.cjs';
6
+ export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, f as IDataExportContributor, M as MESSAGE_KEYS, g as buildAuthAccountLimiter, h as buildAuthIpLimiter, w as withSession } from './session-6B77MAFn.cjs';
7
7
  export { IUserDTO, toUserDTO } from './dtos/user.cjs';
8
8
  export { requireAuth, validate } from '@fonderie/core/middlewares';
9
9
  import { z } from 'zod';
@@ -107,10 +107,13 @@ declare const refreshSchema: z.ZodObject<{
107
107
  declare const forgotPasswordSchema: z.ZodObject<{
108
108
  email: z.ZodPipe<z.ZodString, z.ZodEmail>;
109
109
  }, z.core.$strip>;
110
- declare const resetPasswordSchema: z.ZodObject<{
110
+ declare const resetPasswordSchema: z.ZodUnion<readonly [z.ZodObject<{
111
111
  pin: z.ZodString;
112
112
  password: z.ZodString;
113
- }, z.core.$strip>;
113
+ }, z.core.$strip>, z.ZodObject<{
114
+ token: z.ZodString;
115
+ password: z.ZodString;
116
+ }, z.core.$strip>]>;
114
117
  declare const verifySchema: z.ZodObject<{
115
118
  token: z.ZodString;
116
119
  }, z.core.$strip>;
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ export { IMfaChallenge, ISession, IUser } from './types.js';
2
2
  import { IFonderieModule, IReadinessProblem, IFonderieApp } from '@fonderie/core';
3
3
  import { IStoreAdapter } from '@fonderie/store';
4
4
  import { EventBus } from '@fonderie/events';
5
- import { I as IAuthConfig } from './session-Db6KZca3.js';
6
- export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, f as IDataExportContributor, M as MESSAGE_KEYS, g as buildAuthAccountLimiter, h as buildAuthIpLimiter, w as withSession } from './session-Db6KZca3.js';
5
+ import { I as IAuthConfig } from './session-6B77MAFn.js';
6
+ export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, f as IDataExportContributor, M as MESSAGE_KEYS, g as buildAuthAccountLimiter, h as buildAuthIpLimiter, w as withSession } from './session-6B77MAFn.js';
7
7
  export { IUserDTO, toUserDTO } from './dtos/user.js';
8
8
  export { requireAuth, validate } from '@fonderie/core/middlewares';
9
9
  import { z } from 'zod';
@@ -107,10 +107,13 @@ declare const refreshSchema: z.ZodObject<{
107
107
  declare const forgotPasswordSchema: z.ZodObject<{
108
108
  email: z.ZodPipe<z.ZodString, z.ZodEmail>;
109
109
  }, z.core.$strip>;
110
- declare const resetPasswordSchema: z.ZodObject<{
110
+ declare const resetPasswordSchema: z.ZodUnion<readonly [z.ZodObject<{
111
111
  pin: z.ZodString;
112
112
  password: z.ZodString;
113
- }, z.core.$strip>;
113
+ }, z.core.$strip>, z.ZodObject<{
114
+ token: z.ZodString;
115
+ password: z.ZodString;
116
+ }, z.core.$strip>]>;
114
117
  declare const verifySchema: z.ZodObject<{
115
118
  token: z.ZodString;
116
119
  }, z.core.$strip>;
package/dist/index.js CHANGED
@@ -161,7 +161,10 @@ var loginSchema = z.union([
161
161
  ]);
162
162
  var refreshSchema = z.object({ refreshToken: z.string().min(1).optional() });
163
163
  var forgotPasswordSchema = z.object({ email });
164
- var resetPasswordSchema = z.object({ pin: sixDigitPin, password });
164
+ var resetPasswordSchema = z.union([
165
+ z.object({ pin: sixDigitPin, password }),
166
+ z.object({ token: z.string().trim().min(32, "invalid reset token"), password })
167
+ ]);
165
168
  var verifySchema = z.object({ token: sixDigitPin });
166
169
  var updateProfileSchema = z.object({
167
170
  firstName: z.string().max(100).nullable().optional(),
@@ -1106,7 +1109,7 @@ function mfaController(store, config, issuer, bus) {
1106
1109
  }
1107
1110
 
1108
1111
  // src/controllers/auth.controller.ts
1109
- import { randomInt } from "crypto";
1112
+ import { randomBytes as randomBytes3, randomInt } from "crypto";
1110
1113
  import { NOTIFICATION_EVENT as NOTIFICATION_EVENT2 } from "@fonderie/events";
1111
1114
  import { setApiResponse as setApiResponse3, HTTP as HTTP3 } from "@fonderie/core";
1112
1115
 
@@ -1153,18 +1156,20 @@ function normalizeEmailSafe(email2) {
1153
1156
  }
1154
1157
 
1155
1158
  // src/models/password-reset.model.ts
1159
+ import { createHash } from "crypto";
1160
+ var hashSecret = (value) => createHash("sha256").update(value).digest("hex");
1156
1161
  var PasswordResetModel = class {
1157
1162
  constructor(store) {
1158
1163
  this.store = store;
1159
1164
  }
1160
1165
  store;
1161
- async create(userId, pin, expiresAt) {
1166
+ async create(userId, pin, token, expiresAt) {
1162
1167
  await this.store.query(
1163
- `INSERT INTO fonderie_password_resets (user_id, pin, expires_at, created_at)
1164
- VALUES ($1, $2, $3, now())
1168
+ `INSERT INTO fonderie_password_resets (user_id, pin, token, expires_at, created_at)
1169
+ VALUES ($1, $2, $3, $4, now())
1165
1170
  ON CONFLICT (user_id) DO UPDATE
1166
- SET pin = $2, expires_at = $3, created_at = now()`,
1167
- [userId, pin, expiresAt]
1171
+ SET pin = $2, token = $3, expires_at = $4, created_at = now()`,
1172
+ [userId, hashSecret(pin), hashSecret(token), expiresAt]
1168
1173
  );
1169
1174
  }
1170
1175
  async findLastSentAt(userId) {
@@ -1178,7 +1183,20 @@ var PasswordResetModel = class {
1178
1183
  async findByPin(pin) {
1179
1184
  const [row] = await this.store.query(
1180
1185
  `SELECT user_id, expires_at FROM fonderie_password_resets WHERE pin = $1`,
1181
- [pin]
1186
+ [hashSecret(pin)]
1187
+ );
1188
+ if (!row) return null;
1189
+ return { userId: row.user_id, expiresAt: new Date(row.expires_at) };
1190
+ }
1191
+ // Lookup by the high-entropy reset token. Unlike the 6-digit pin this is
1192
+ // not brute-forceable, so the token path needs no rate limit. Empty/short
1193
+ // values are refused before the query so a NULL/blank token column can
1194
+ // never be matched.
1195
+ async findByToken(token) {
1196
+ if (typeof token !== "string" || token.length < 32) return null;
1197
+ const [row] = await this.store.query(
1198
+ `SELECT user_id, expires_at FROM fonderie_password_resets WHERE token = $1`,
1199
+ [hashSecret(token)]
1182
1200
  );
1183
1201
  if (!row) return null;
1184
1202
  return { userId: row.user_id, expiresAt: new Date(row.expires_at) };
@@ -1652,13 +1670,16 @@ function authController(store, config, bus) {
1652
1670
  );
1653
1671
  }
1654
1672
  const pin = randomInt(1e5, 1e6).toString();
1673
+ const token = randomBytes3(32).toString("hex");
1655
1674
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60);
1656
- await passwordReset.create(user.id, pin, expiresAt);
1675
+ await passwordReset.create(user.id, pin, token, expiresAt);
1676
+ const base = resolved.passwordResetUrl;
1677
+ const resetUrl = base ? `${base}${base.includes("?") ? "&" : "?"}token=${token}` : "";
1657
1678
  bus?.emit(NOTIFICATION_EVENT2, {
1658
1679
  type: MESSAGE_KEYS.passwordReset,
1659
1680
  locale: user.locale,
1660
1681
  recipient: { email: email2, phone: null, deviceToken: null },
1661
- data: { pin }
1682
+ data: { pin, token, resetUrl }
1662
1683
  }).catch(() => {
1663
1684
  });
1664
1685
  return setApiResponse3(
@@ -1671,19 +1692,9 @@ function authController(store, config, bus) {
1671
1692
  const body = ctx.meta["body"];
1672
1693
  const raw = body?.["pin"];
1673
1694
  const password2 = body?.["password"];
1674
- if (typeof raw !== "string" || typeof password2 !== "string") {
1675
- return setApiResponse3(
1676
- HTTP3.UNPROCESSABLE,
1677
- "INVALID_PARAMETER",
1678
- "pin and password are required"
1679
- );
1680
- }
1681
- if (!/^\d{6}$/.test(raw.trim())) {
1682
- return setApiResponse3(
1683
- HTTP3.UNPROCESSABLE,
1684
- "INVALID_PARAMETER",
1685
- "pin must be a 6-digit code"
1686
- );
1695
+ const rawToken = body?.["token"];
1696
+ if (typeof password2 !== "string") {
1697
+ return setApiResponse3(HTTP3.UNPROCESSABLE, "INVALID_PARAMETER", "password is required");
1687
1698
  }
1688
1699
  if (password2.length < 8) {
1689
1700
  return setApiResponse3(
@@ -1692,10 +1703,25 @@ function authController(store, config, bus) {
1692
1703
  "password must be at least 8 characters"
1693
1704
  );
1694
1705
  }
1695
- const pin = raw.trim();
1696
- const row = await passwordReset.findByPin(pin);
1706
+ let row;
1707
+ if (typeof rawToken === "string" && rawToken.trim().length >= 32) {
1708
+ row = await passwordReset.findByToken(rawToken.trim());
1709
+ } else {
1710
+ if (typeof raw !== "string" || !/^\d{6}$/.test(raw.trim())) {
1711
+ return setApiResponse3(
1712
+ HTTP3.UNPROCESSABLE,
1713
+ "INVALID_PARAMETER",
1714
+ "a 6-digit pin or a reset token is required"
1715
+ );
1716
+ }
1717
+ row = await passwordReset.findByPin(raw.trim());
1718
+ }
1697
1719
  if (!row || /* @__PURE__ */ new Date() > row.expiresAt) {
1698
- return setApiResponse3(HTTP3.BAD_REQUEST, "PASSWORD_RESET_FAILED", "Invalid or expired pin");
1720
+ return setApiResponse3(
1721
+ HTTP3.BAD_REQUEST,
1722
+ "PASSWORD_RESET_FAILED",
1723
+ "Invalid or expired reset credentials"
1724
+ );
1699
1725
  }
1700
1726
  const passwordHash = await hashPassword(password2);
1701
1727
  await store.transaction(async (tx) => {
@@ -2228,7 +2254,7 @@ function userController(store, config, bus) {
2228
2254
  }
2229
2255
 
2230
2256
  // src/controllers/oauth.controller.ts
2231
- import { randomBytes as randomBytes3 } from "crypto";
2257
+ import { randomBytes as randomBytes4 } from "crypto";
2232
2258
  import { setApiResponse as setApiResponse5, HTTP as HTTP5, constantTimeEqual as constantTimeEqual2 } from "@fonderie/core";
2233
2259
  function oauthController(store, config) {
2234
2260
  const users = new UserModel(store);
@@ -2244,7 +2270,7 @@ function oauthController(store, config) {
2244
2270
  "Google OAuth not configured"
2245
2271
  );
2246
2272
  }
2247
- const state = randomBytes3(16).toString("hex");
2273
+ const state = randomBytes4(16).toString("hex");
2248
2274
  const params = new URLSearchParams({
2249
2275
  client_id: google.clientId,
2250
2276
  redirect_uri: google.redirectUri,
@@ -2710,7 +2736,11 @@ If you made this change, you're all set. If not, contact support right away \u20
2710
2736
  var SAMPLE_PAYLOADS = {
2711
2737
  [MESSAGE_KEYS.emailRegistration]: { firstName: "", pin: "123456" },
2712
2738
  [MESSAGE_KEYS.emailVerification]: { firstName: "Ada", pin: "123456" },
2713
- [MESSAGE_KEYS.passwordReset]: { pin: "123456" },
2739
+ [MESSAGE_KEYS.passwordReset]: {
2740
+ pin: "123456",
2741
+ token: "0".repeat(64),
2742
+ resetUrl: "https://app.example.com/reset?token=" + "0".repeat(64)
2743
+ },
2714
2744
  [MESSAGE_KEYS.phoneOtp]: { otp: "123456" },
2715
2745
  [MESSAGE_KEYS.mfaEnabled]: {},
2716
2746
  [MESSAGE_KEYS.mfaDisabled]: {},