@fonderie/auth 5.2.0 → 6.0.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 +3 -3
- package/brain/signatures.md +1 -1
- package/dist/index.cjs +29 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +29 -25
- 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/{session-DJNd1xp_.d.cts → session-Db6KZca3.d.cts} +1 -1
- package/dist/{session-DJNd1xp_.d.ts → session-Db6KZca3.d.ts} +1 -1
- package/package.json +6 -6
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-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';
|
|
7
7
|
export { IUserDTO, toUserDTO } from './dtos/user.cjs';
|
|
8
8
|
export { requireAuth, validate } from '@fonderie/core/middlewares';
|
|
9
9
|
import { z } from 'zod';
|
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-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';
|
|
7
7
|
export { IUserDTO, toUserDTO } from './dtos/user.js';
|
|
8
8
|
export { requireAuth, validate } from '@fonderie/core/middlewares';
|
|
9
9
|
import { z } from 'zod';
|
package/dist/index.js
CHANGED
|
@@ -86,6 +86,15 @@ var DEFAULTS = {
|
|
|
86
86
|
account: { capacity: 3, refillPerSec: 3 / min(60) },
|
|
87
87
|
accountField: "email"
|
|
88
88
|
},
|
|
89
|
+
// reset: 10/15min per IP — the reset PIN is a 6-digit code looked up
|
|
90
|
+
// globally, so an unthrottled endpoint is brute-forceable into an account
|
|
91
|
+
// takeover. There's no email in the reset body to key an account bucket on,
|
|
92
|
+
// so this IP bucket is the guard. (Moving reset to a high-entropy opaque
|
|
93
|
+
// token is tracked as a follow-up hardening.)
|
|
94
|
+
reset: { ip: { capacity: 10, refillPerSec: 10 / min(15) } },
|
|
95
|
+
// verify: 10/15min per IP — /auth/verify checks a 6-digit email/phone OTP,
|
|
96
|
+
// and in the phone flow that OTP is the login credential itself.
|
|
97
|
+
verify: { ip: { capacity: 10, refillPerSec: 10 / min(15) } },
|
|
89
98
|
// mfaVerify: 10/15min per IP — TOTP brute-force.
|
|
90
99
|
mfaVerify: { ip: { capacity: 10, refillPerSec: 10 / min(15) } }
|
|
91
100
|
};
|
|
@@ -1418,23 +1427,17 @@ function authController(store, config, bus) {
|
|
|
1418
1427
|
reqOpts2
|
|
1419
1428
|
).catch(() => {
|
|
1420
1429
|
});
|
|
1421
|
-
const
|
|
1422
|
-
loginMethod: "phone"
|
|
1423
|
-
});
|
|
1424
|
-
await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, requestMeta(ctx));
|
|
1430
|
+
const otpToken = issueMfaPendingToken(user.id, config, "phone");
|
|
1425
1431
|
return Response.json(
|
|
1426
1432
|
{
|
|
1427
1433
|
reason: "USER_PHONE_REGISTERED",
|
|
1428
1434
|
explanation: "Account created. A verification code has been sent to your phone.",
|
|
1429
1435
|
result: {
|
|
1430
|
-
|
|
1436
|
+
otpToken,
|
|
1431
1437
|
user: toUserDTO(user)
|
|
1432
1438
|
}
|
|
1433
1439
|
},
|
|
1434
|
-
{
|
|
1435
|
-
status: 202,
|
|
1436
|
-
headers: cookieHeaders(tokenPairCookies(accessToken, refreshToken, config))
|
|
1437
|
-
}
|
|
1440
|
+
{ status: 202 }
|
|
1438
1441
|
);
|
|
1439
1442
|
}
|
|
1440
1443
|
return setApiResponse3(
|
|
@@ -1548,23 +1551,14 @@ function authController(store, config, bus) {
|
|
|
1548
1551
|
recipient: { email: null, phone: normalizePhone(phone2), deviceToken: null }
|
|
1549
1552
|
}).catch(() => {
|
|
1550
1553
|
});
|
|
1551
|
-
const
|
|
1552
|
-
loginMethod: "phone"
|
|
1553
|
-
});
|
|
1554
|
-
await sessions.create(user.id, refreshToken, refreshTokenExpiry(refreshToken), sid, meta);
|
|
1554
|
+
const otpToken = issueMfaPendingToken(user.id, config, "phone");
|
|
1555
1555
|
return Response.json(
|
|
1556
1556
|
{
|
|
1557
1557
|
reason: "USER_PHONE_OTP_SENT",
|
|
1558
1558
|
explanation: "A verification code has been sent to your phone.",
|
|
1559
|
-
result: {
|
|
1560
|
-
tokens: { access: accessToken, refresh: refreshToken },
|
|
1561
|
-
user: toUserDTO(user, false)
|
|
1562
|
-
}
|
|
1559
|
+
result: { otpToken }
|
|
1563
1560
|
},
|
|
1564
|
-
{
|
|
1565
|
-
status: 202,
|
|
1566
|
-
headers: cookieHeaders(tokenPairCookies(accessToken, refreshToken, config))
|
|
1567
|
-
}
|
|
1561
|
+
{ status: 202 }
|
|
1568
1562
|
);
|
|
1569
1563
|
}
|
|
1570
1564
|
return setApiResponse3(
|
|
@@ -1716,6 +1710,9 @@ function authController(store, config, bus) {
|
|
|
1716
1710
|
return setApiResponse3(HTTP3.OK, "PASSWORD_RESET_SUCCESSFUL", "Password reset successfully.");
|
|
1717
1711
|
},
|
|
1718
1712
|
verify: async (ctx) => {
|
|
1713
|
+
if (ctx.user.mfaPending && ctx.user.loginMethod !== "phone") {
|
|
1714
|
+
return setApiResponse3(HTTP3.FORBIDDEN, "MFA_REQUIRED", "Complete MFA verification to continue");
|
|
1715
|
+
}
|
|
1719
1716
|
if (ctx.user.loginMethod !== "phone" && ctx.user.emailVerifiedAt) {
|
|
1720
1717
|
return setApiResponse3(HTTP3.OK, "VERIFIED", "Email verified successfully.", {
|
|
1721
1718
|
verified: true,
|
|
@@ -1802,6 +1799,9 @@ function authController(store, config, bus) {
|
|
|
1802
1799
|
});
|
|
1803
1800
|
},
|
|
1804
1801
|
sendVerification: async (ctx) => {
|
|
1802
|
+
if (ctx.user.mfaPending && ctx.user.loginMethod !== "phone") {
|
|
1803
|
+
return setApiResponse3(HTTP3.FORBIDDEN, "MFA_REQUIRED", "Complete MFA verification to continue");
|
|
1804
|
+
}
|
|
1805
1805
|
const resolved = { ...config, ...config.resolve?.(ctx) };
|
|
1806
1806
|
const cooldown = resolved.verificationCooldown ?? DEFAULT_VERIFICATION_COOLDOWN;
|
|
1807
1807
|
if (ctx.user.loginMethod === "phone") {
|
|
@@ -2367,10 +2367,14 @@ function buildAuthRoutes(store, config, bus) {
|
|
|
2367
2367
|
R("refresh", "POST", "/auth/refresh", validate(refreshSchema), auth.refresh),
|
|
2368
2368
|
// Email — Password Recovery (Public)
|
|
2369
2369
|
R("forgotPassword", "POST", "/auth/email/forgot", ipLimit("forgot"), validate(forgotPasswordSchema), acctLimit("forgot"), auth.forgotPassword),
|
|
2370
|
-
R("resetPassword", "POST", "/auth/email/reset", validate(resetPasswordSchema), auth.resetPassword),
|
|
2371
|
-
// Verification (
|
|
2372
|
-
|
|
2373
|
-
|
|
2370
|
+
R("resetPassword", "POST", "/auth/email/reset", ipLimit("reset"), validate(resetPasswordSchema), auth.resetPassword),
|
|
2371
|
+
// Verification (email or phone, determined by loginMethod). requireAnyAuth,
|
|
2372
|
+
// not requireAuth: the phone-OTP flow reaches these with a short-lived
|
|
2373
|
+
// pending token (login/register no longer issue real tokens before the OTP
|
|
2374
|
+
// round-trip); the controllers reject pending tokens for any other flow.
|
|
2375
|
+
// ipLimit guards the 6-digit OTP against brute force (like mfaVerify).
|
|
2376
|
+
R("verifyEmail", "POST", "/auth/verify", ipLimit("verify"), requireAnyAuth, validate(verifySchema), auth.verify),
|
|
2377
|
+
R("sendVerification", "GET", "/auth/send-verification", requireAnyAuth, auth.sendVerification),
|
|
2374
2378
|
// Account Management (Protected)
|
|
2375
2379
|
R("logout", "POST", "/auth/logout", requireAuth, validate(refreshSchema), auth.logout),
|
|
2376
2380
|
// User Profile (Protected; writes also gate on requireVerification)
|