@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/brain/outcomes.md +1 -0
- package/brain/signatures.md +1 -0
- package/dist/index.cjs +68 -38
- 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 +59 -29
- 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 +1 -1
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
|
|
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
);
|
|
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
|
-
|
|
1696
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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]: {
|
|
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]: {},
|