@fonderie/auth 7.0.0 → 7.1.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.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
 
@@ -1158,13 +1161,13 @@ var PasswordResetModel = class {
1158
1161
  this.store = store;
1159
1162
  }
1160
1163
  store;
1161
- async create(userId, pin, expiresAt) {
1164
+ async create(userId, pin, token, expiresAt) {
1162
1165
  await this.store.query(
1163
- `INSERT INTO fonderie_password_resets (user_id, pin, expires_at, created_at)
1164
- VALUES ($1, $2, $3, now())
1166
+ `INSERT INTO fonderie_password_resets (user_id, pin, token, expires_at, created_at)
1167
+ VALUES ($1, $2, $3, $4, now())
1165
1168
  ON CONFLICT (user_id) DO UPDATE
1166
- SET pin = $2, expires_at = $3, created_at = now()`,
1167
- [userId, pin, expiresAt]
1169
+ SET pin = $2, token = $3, expires_at = $4, created_at = now()`,
1170
+ [userId, pin, token, expiresAt]
1168
1171
  );
1169
1172
  }
1170
1173
  async findLastSentAt(userId) {
@@ -1183,6 +1186,19 @@ var PasswordResetModel = class {
1183
1186
  if (!row) return null;
1184
1187
  return { userId: row.user_id, expiresAt: new Date(row.expires_at) };
1185
1188
  }
1189
+ // Lookup by the high-entropy reset token. Unlike the 6-digit pin this is
1190
+ // not brute-forceable, so the token path needs no rate limit. Empty/short
1191
+ // values are refused before the query so a NULL/blank token column can
1192
+ // never be matched.
1193
+ async findByToken(token) {
1194
+ if (typeof token !== "string" || token.length < 32) return null;
1195
+ const [row] = await this.store.query(
1196
+ `SELECT user_id, expires_at FROM fonderie_password_resets WHERE token = $1`,
1197
+ [token]
1198
+ );
1199
+ if (!row) return null;
1200
+ return { userId: row.user_id, expiresAt: new Date(row.expires_at) };
1201
+ }
1186
1202
  async deleteByUser(userId) {
1187
1203
  await this.store.query(`DELETE FROM fonderie_password_resets WHERE user_id = $1`, [userId]);
1188
1204
  }
@@ -1652,13 +1668,16 @@ function authController(store, config, bus) {
1652
1668
  );
1653
1669
  }
1654
1670
  const pin = randomInt(1e5, 1e6).toString();
1671
+ const token = randomBytes3(32).toString("hex");
1655
1672
  const expiresAt = new Date(Date.now() + 1e3 * 60 * 60);
1656
- await passwordReset.create(user.id, pin, expiresAt);
1673
+ await passwordReset.create(user.id, pin, token, expiresAt);
1674
+ const base = resolved.passwordResetUrl;
1675
+ const resetUrl = base ? `${base}${base.includes("?") ? "&" : "?"}token=${token}` : "";
1657
1676
  bus?.emit(NOTIFICATION_EVENT2, {
1658
1677
  type: MESSAGE_KEYS.passwordReset,
1659
1678
  locale: user.locale,
1660
1679
  recipient: { email: email2, phone: null, deviceToken: null },
1661
- data: { pin }
1680
+ data: { pin, token, resetUrl }
1662
1681
  }).catch(() => {
1663
1682
  });
1664
1683
  return setApiResponse3(
@@ -1671,19 +1690,9 @@ function authController(store, config, bus) {
1671
1690
  const body = ctx.meta["body"];
1672
1691
  const raw = body?.["pin"];
1673
1692
  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
- );
1693
+ const rawToken = body?.["token"];
1694
+ if (typeof password2 !== "string") {
1695
+ return setApiResponse3(HTTP3.UNPROCESSABLE, "INVALID_PARAMETER", "password is required");
1687
1696
  }
1688
1697
  if (password2.length < 8) {
1689
1698
  return setApiResponse3(
@@ -1692,10 +1701,25 @@ function authController(store, config, bus) {
1692
1701
  "password must be at least 8 characters"
1693
1702
  );
1694
1703
  }
1695
- const pin = raw.trim();
1696
- const row = await passwordReset.findByPin(pin);
1704
+ let row;
1705
+ if (typeof rawToken === "string" && rawToken.trim().length >= 32) {
1706
+ row = await passwordReset.findByToken(rawToken.trim());
1707
+ } else {
1708
+ if (typeof raw !== "string" || !/^\d{6}$/.test(raw.trim())) {
1709
+ return setApiResponse3(
1710
+ HTTP3.UNPROCESSABLE,
1711
+ "INVALID_PARAMETER",
1712
+ "a 6-digit pin or a reset token is required"
1713
+ );
1714
+ }
1715
+ row = await passwordReset.findByPin(raw.trim());
1716
+ }
1697
1717
  if (!row || /* @__PURE__ */ new Date() > row.expiresAt) {
1698
- return setApiResponse3(HTTP3.BAD_REQUEST, "PASSWORD_RESET_FAILED", "Invalid or expired pin");
1718
+ return setApiResponse3(
1719
+ HTTP3.BAD_REQUEST,
1720
+ "PASSWORD_RESET_FAILED",
1721
+ "Invalid or expired reset credentials"
1722
+ );
1699
1723
  }
1700
1724
  const passwordHash = await hashPassword(password2);
1701
1725
  await store.transaction(async (tx) => {
@@ -2228,7 +2252,7 @@ function userController(store, config, bus) {
2228
2252
  }
2229
2253
 
2230
2254
  // src/controllers/oauth.controller.ts
2231
- import { randomBytes as randomBytes3 } from "crypto";
2255
+ import { randomBytes as randomBytes4 } from "crypto";
2232
2256
  import { setApiResponse as setApiResponse5, HTTP as HTTP5, constantTimeEqual as constantTimeEqual2 } from "@fonderie/core";
2233
2257
  function oauthController(store, config) {
2234
2258
  const users = new UserModel(store);
@@ -2244,7 +2268,7 @@ function oauthController(store, config) {
2244
2268
  "Google OAuth not configured"
2245
2269
  );
2246
2270
  }
2247
- const state = randomBytes3(16).toString("hex");
2271
+ const state = randomBytes4(16).toString("hex");
2248
2272
  const params = new URLSearchParams({
2249
2273
  client_id: google.clientId,
2250
2274
  redirect_uri: google.redirectUri,
@@ -2710,7 +2734,11 @@ If you made this change, you're all set. If not, contact support right away \u20
2710
2734
  var SAMPLE_PAYLOADS = {
2711
2735
  [MESSAGE_KEYS.emailRegistration]: { firstName: "", pin: "123456" },
2712
2736
  [MESSAGE_KEYS.emailVerification]: { firstName: "Ada", pin: "123456" },
2713
- [MESSAGE_KEYS.passwordReset]: { pin: "123456" },
2737
+ [MESSAGE_KEYS.passwordReset]: {
2738
+ pin: "123456",
2739
+ token: "0".repeat(64),
2740
+ resetUrl: "https://app.example.com/reset?token=" + "0".repeat(64)
2741
+ },
2714
2742
  [MESSAGE_KEYS.phoneOtp]: { otp: "123456" },
2715
2743
  [MESSAGE_KEYS.mfaEnabled]: {},
2716
2744
  [MESSAGE_KEYS.mfaDisabled]: {},