@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/brain/outcomes.md +1 -0
- package/brain/signatures.md +1 -0
- package/dist/index.cjs +53 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +56 -28
- package/dist/index.js.map +1 -1
- package/dist/middlewares/index.d.cts +1 -1
- package/dist/middlewares/index.d.ts +1 -1
- package/dist/migrations/sql/015_password_reset_token.sql +11 -0
- package/dist/{session-Db6KZca3.d.cts → session-6B77MAFn.d.cts} +1 -0
- package/dist/{session-Db6KZca3.d.ts → session-6B77MAFn.d.ts} +1 -0
- package/package.json +3 -3
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
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
|
-
|
|
1696
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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]: {
|
|
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]: {},
|