@better-forge/auth 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/apiTokenRoutes.d.ts +8 -0
- package/dist/apiTokenRoutes.d.ts.map +1 -0
- package/dist/apiTokenRoutes.js +67 -0
- package/dist/apiTokenRoutes.js.map +1 -0
- package/dist/apiTokens.d.ts +25 -0
- package/dist/apiTokens.d.ts.map +1 -0
- package/dist/apiTokens.js +50 -0
- package/dist/apiTokens.js.map +1 -0
- package/dist/authRoutes.d.ts +37 -0
- package/dist/authRoutes.d.ts.map +1 -0
- package/dist/authRoutes.js +403 -0
- package/dist/authRoutes.js.map +1 -0
- package/dist/context.d.ts +98 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/createAuth.d.ts +146 -0
- package/dist/createAuth.d.ts.map +1 -0
- package/dist/createAuth.js +52 -0
- package/dist/createAuth.js.map +1 -0
- package/dist/identityConfirmation.d.ts +13 -0
- package/dist/identityConfirmation.d.ts.map +1 -0
- package/dist/identityConfirmation.js +37 -0
- package/dist/identityConfirmation.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +41 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +100 -0
- package/dist/middleware.js.map +1 -0
- package/dist/migrations.d.ts +4 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +77 -0
- package/dist/migrations.js.map +1 -0
- package/dist/module.d.ts +8 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +5 -0
- package/dist/module.js.map +1 -0
- package/dist/oidc.d.ts +85 -0
- package/dist/oidc.d.ts.map +1 -0
- package/dist/oidc.js +213 -0
- package/dist/oidc.js.map +1 -0
- package/dist/oidcConfig.d.ts +39 -0
- package/dist/oidcConfig.d.ts.map +1 -0
- package/dist/oidcConfig.js +72 -0
- package/dist/oidcConfig.js.map +1 -0
- package/dist/oidcSettings.d.ts +15 -0
- package/dist/oidcSettings.d.ts.map +1 -0
- package/dist/oidcSettings.js +51 -0
- package/dist/oidcSettings.js.map +1 -0
- package/dist/oidcSettingsRoutes.d.ts +19 -0
- package/dist/oidcSettingsRoutes.d.ts.map +1 -0
- package/dist/oidcSettingsRoutes.js +133 -0
- package/dist/oidcSettingsRoutes.js.map +1 -0
- package/dist/roles.d.ts +25 -0
- package/dist/roles.d.ts.map +1 -0
- package/dist/roles.js +44 -0
- package/dist/roles.js.map +1 -0
- package/dist/routeScope.d.ts +34 -0
- package/dist/routeScope.d.ts.map +1 -0
- package/dist/routeScope.js +32 -0
- package/dist/routeScope.js.map +1 -0
- package/dist/sessions.d.ts +46 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +159 -0
- package/dist/sessions.js.map +1 -0
- package/dist/setupRoutes.d.ts +37 -0
- package/dist/setupRoutes.d.ts.map +1 -0
- package/dist/setupRoutes.js +70 -0
- package/dist/setupRoutes.js.map +1 -0
- package/dist/signInAudit.d.ts +19 -0
- package/dist/signInAudit.d.ts.map +1 -0
- package/dist/signInAudit.js +16 -0
- package/dist/signInAudit.js.map +1 -0
- package/dist/tables.d.ts +716 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +56 -0
- package/dist/tables.js.map +1 -0
- package/dist/tokenCatalogue.d.ts +21 -0
- package/dist/tokenCatalogue.d.ts.map +1 -0
- package/dist/tokenCatalogue.js +27 -0
- package/dist/tokenCatalogue.js.map +1 -0
- package/dist/totp.d.ts +12 -0
- package/dist/totp.d.ts.map +1 -0
- package/dist/totp.js +93 -0
- package/dist/totp.js.map +1 -0
- package/dist/twoFactor.d.ts +10 -0
- package/dist/twoFactor.d.ts.map +1 -0
- package/dist/twoFactor.js +43 -0
- package/dist/twoFactor.js.map +1 -0
- package/dist/userRoutes.d.ts +13 -0
- package/dist/userRoutes.d.ts.map +1 -0
- package/dist/userRoutes.js +135 -0
- package/dist/userRoutes.js.map +1 -0
- package/package.json +66 -4
- package/src/apiTokenRoutes.ts +82 -0
- package/src/apiTokens.ts +62 -0
- package/src/authRoutes.ts +452 -0
- package/src/context.ts +85 -0
- package/src/createAuth.ts +58 -0
- package/src/identityConfirmation.ts +50 -0
- package/src/index.ts +17 -0
- package/src/middleware.ts +123 -0
- package/src/migrations.ts +77 -0
- package/src/module.ts +6 -0
- package/src/oidc.ts +291 -0
- package/src/oidcConfig.ts +96 -0
- package/src/oidcSettings.ts +60 -0
- package/src/oidcSettingsRoutes.ts +165 -0
- package/src/roles.ts +60 -0
- package/src/routeScope.ts +50 -0
- package/src/sessions.ts +209 -0
- package/src/setupRoutes.ts +100 -0
- package/src/signInAudit.ts +43 -0
- package/src/tables.ts +59 -0
- package/src/tokenCatalogue.ts +42 -0
- package/src/totp.ts +96 -0
- package/src/twoFactor.ts +49 -0
- package/src/userRoutes.ts +144 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import bcrypt from 'bcryptjs';
|
|
3
|
+
import QRCode from 'qrcode';
|
|
4
|
+
import { eq } from 'drizzle-orm';
|
|
5
|
+
import { users } from './tables.js';
|
|
6
|
+
import { generateRecoveryCodes, generateTotpSecret, hashRecoveryCode, totpUri, verifyTotp } from './totp.js';
|
|
7
|
+
import { ALLOW_PENDING_PASSWORD_CHANGE, requestIdentity } from './middleware.js';
|
|
8
|
+
import { passwordLoginEnabled } from './oidcConfig.js';
|
|
9
|
+
import { OIDC_CONFIRMATION_MAX_AGE_SECONDS } from './oidc.js';
|
|
10
|
+
export function createSignInRoutes(ctx, parts) {
|
|
11
|
+
const { db, audit, secretBox, cookies, urls, basePath, totpIssuer } = ctx;
|
|
12
|
+
const returnTargets = ctx.returnTargets ?? [];
|
|
13
|
+
const hooks = ctx.hooks ?? {};
|
|
14
|
+
const { requireAuth } = parts.middleware;
|
|
15
|
+
const { clearAuthCookies, createAuthSession, revokeSession, revokeUserSessions } = parts.sessions;
|
|
16
|
+
const { verifySecondFactor } = parts.twoFactor;
|
|
17
|
+
const { confirmIdentity, markSessionVerified } = parts.identityConfirmation;
|
|
18
|
+
const { auditSignIn, auditSignInDenial } = parts.signInAudit;
|
|
19
|
+
const { getOidcConfig, getOidcTestConfig } = parts.oidcSettings;
|
|
20
|
+
const { auditOidcDenial, beginOidcLogin, evaluateOidcClaims, exchangeOidcCode, linkOidcIdentity, provisionSsoUser, runOidcTest, storeOidcTestResult } = parts.oidc;
|
|
21
|
+
const OIDC_FLOW_COOKIE = cookies.oidcFlow;
|
|
22
|
+
const OIDC_FLOW_PATH = `${basePath}/oidc`;
|
|
23
|
+
const OIDC_FAILED_REDIRECT = `${urls.signIn}?error=oidc_failed`;
|
|
24
|
+
const SSO_CONFIRM_PAGE = urls.ssoConfirm;
|
|
25
|
+
const TWO_FACTOR_CHALLENGE_COOKIE = cookies.twoFactorChallenge;
|
|
26
|
+
const TWO_FACTOR_CHALLENGE_PATH = `${basePath}/2fa`;
|
|
27
|
+
const OIDC_FLOW_SECONDS = 10 * 60;
|
|
28
|
+
const TWO_FACTOR_CHALLENGE_SECONDS = 5 * 60;
|
|
29
|
+
// Denials about the identity itself show "no account matches"; the rest show the generic failure.
|
|
30
|
+
const ACCOUNT_DENIALS = new Set(['no_email_claim', 'email_not_verified', 'no_matching_account', 'subject_mismatch']);
|
|
31
|
+
const errorText = (error) => (error instanceof Error ? error.message : String(error)).slice(0, 1000);
|
|
32
|
+
/** Redirects to the provider, keeping the PKCE verifier, state and nonce in a short-lived signed cookie. */
|
|
33
|
+
async function startOidcFlow(app, reply, cfg, extra) {
|
|
34
|
+
const { url, flow } = await beginOidcLogin(cfg, { confirm: extra.purpose === 'oidc-confirm' });
|
|
35
|
+
reply.setCookie(OIDC_FLOW_COOKIE, app.jwt.sign({ ...extra, ...flow }, { expiresIn: OIDC_FLOW_SECONDS }), {
|
|
36
|
+
path: OIDC_FLOW_PATH,
|
|
37
|
+
httpOnly: true,
|
|
38
|
+
secure: ctx.secureCookies(),
|
|
39
|
+
// Lax, not Strict: the IdP redirects back from another site and Strict cookies are not sent then.
|
|
40
|
+
sameSite: 'lax',
|
|
41
|
+
maxAge: OIDC_FLOW_SECONDS,
|
|
42
|
+
});
|
|
43
|
+
return reply.redirect(url);
|
|
44
|
+
}
|
|
45
|
+
function callbackUrlFor(cfg, query) {
|
|
46
|
+
const url = new URL(cfg.redirectUri);
|
|
47
|
+
url.search = query.toString();
|
|
48
|
+
return url;
|
|
49
|
+
}
|
|
50
|
+
function publicSession(identity) {
|
|
51
|
+
const { sessionId: _sessionId, ...safe } = identity;
|
|
52
|
+
return safe;
|
|
53
|
+
}
|
|
54
|
+
async function verifyPassword(user, password) {
|
|
55
|
+
return bcrypt.compare(password, user.passwordHash);
|
|
56
|
+
}
|
|
57
|
+
/** Records a refused password sign-in, or a refused 2FA step of either kind of sign-in. */
|
|
58
|
+
function denySignIn(method, code, reason, email) {
|
|
59
|
+
return auditSignInDenial(method, { code, reason, email });
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Makes a pending temporary password unusable once its user signed in through SSO: they did not need it, and the
|
|
63
|
+
* administrator who handed it out still knows it. They can set a password of their own later in Settings.
|
|
64
|
+
*/
|
|
65
|
+
async function revokeTemporaryPassword(user) {
|
|
66
|
+
if (!user.mustChangePassword)
|
|
67
|
+
return user;
|
|
68
|
+
const passwordHash = await bcrypt.hash(randomBytes(32).toString('base64url'), 10);
|
|
69
|
+
await db.update(users).set({ passwordHash, mustChangePassword: 0 }).where(eq(users.id, user.id));
|
|
70
|
+
await audit.write({ userId: user.id, userEmail: user.email }, 'update', 'user-security', user.id, user.email, { temporaryPasswordRevoked: true });
|
|
71
|
+
return { ...user, passwordHash, mustChangePassword: 0 };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Where a finished or failed sign-in goes. The application may send it elsewhere (a `returnTo` target it listed, or
|
|
75
|
+
* a role that belongs on another page); otherwise it ends at `urls.home`, and errors show on `urls.signIn`.
|
|
76
|
+
*/
|
|
77
|
+
function signInRedirect(flow, outcome) {
|
|
78
|
+
const custom = hooks.signInRedirect?.(flow, outcome);
|
|
79
|
+
if (custom)
|
|
80
|
+
return custom;
|
|
81
|
+
if (outcome === 'two-factor')
|
|
82
|
+
return `${urls.signIn}?two-factor=sso`;
|
|
83
|
+
if ('error' in outcome)
|
|
84
|
+
return `${urls.signIn}?error=${outcome.error}`;
|
|
85
|
+
return urls.home;
|
|
86
|
+
}
|
|
87
|
+
/** Ends the SSO confirmation popup; the page tells the opening window through a BroadcastChannel. */
|
|
88
|
+
function confirmRedirect(reply, ok) {
|
|
89
|
+
return reply.redirect(`${SSO_CONFIRM_PAGE}?status=${ok ? 'ok' : 'failed'}`);
|
|
90
|
+
}
|
|
91
|
+
async function finishLogin(app, reply, user, authMethod = 'password') {
|
|
92
|
+
const identity = await createAuthSession(app, reply, user, authMethod);
|
|
93
|
+
return publicSession(identity);
|
|
94
|
+
}
|
|
95
|
+
async function authRoutes(app) {
|
|
96
|
+
app.get('/config', async () => {
|
|
97
|
+
const { config } = await getOidcConfig();
|
|
98
|
+
// `loginUrl` starts SSO on the redirect URI's host: the flow cookie is only sent back to the host that set it,
|
|
99
|
+
// so starting on another one (127.0.0.1 vs localhost, an internal name vs PUBLIC_URL) would lose it.
|
|
100
|
+
return {
|
|
101
|
+
passwordLogin: passwordLoginEnabled(config),
|
|
102
|
+
oidc: config ? { label: config.buttonLabel, loginUrl: `${new URL(config.redirectUri).origin}${OIDC_FLOW_PATH}/login` } : null,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
app.get('/oidc/login', { config: { rateLimit: ctx.rateLimits.signIn } }, async (req, reply) => {
|
|
106
|
+
const { config: cfg } = await getOidcConfig();
|
|
107
|
+
if (!cfg)
|
|
108
|
+
return reply.code(404).send({ error: 'OIDC is not configured' });
|
|
109
|
+
const returnTo = req.query.returnTo;
|
|
110
|
+
const flow = returnTo && returnTargets.includes(returnTo)
|
|
111
|
+
? { purpose: 'oidc-flow', returnTo }
|
|
112
|
+
: { purpose: 'oidc-flow' };
|
|
113
|
+
try {
|
|
114
|
+
return await startOidcFlow(app, reply, cfg, flow);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
req.log.error({ err: error }, 'OIDC discovery failed');
|
|
118
|
+
await auditOidcDenial(cfg, { code: 'discovery_failed', reason: `OIDC discovery against the issuer failed: ${errorText(error)}` });
|
|
119
|
+
return reply.redirect(signInRedirect(flow, { error: 'oidc_failed' }));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
// Opened in a popup by the admin UI when a sensitive action needs an SSO session to be confirmed.
|
|
123
|
+
app.get('/oidc/confirm', { preHandler: requireAuth, config: { rateLimit: ctx.rateLimits.signIn } }, async (req, reply) => {
|
|
124
|
+
const identity = requestIdentity(req);
|
|
125
|
+
const { config: cfg } = await getOidcConfig();
|
|
126
|
+
if (identity.authMethod !== 'oidc' || !cfg)
|
|
127
|
+
return confirmRedirect(reply, false);
|
|
128
|
+
try {
|
|
129
|
+
return await startOidcFlow(app, reply, cfg, { purpose: 'oidc-confirm', sessionId: identity.sessionId, userId: identity.userId });
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
req.log.error({ err: error }, 'OIDC discovery failed');
|
|
133
|
+
await auditOidcDenial(cfg, { code: 'discovery_failed', reason: `OIDC discovery against the issuer failed: ${errorText(error)}` });
|
|
134
|
+
return confirmRedirect(reply, false);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
app.get('/oidc/callback', { config: { rateLimit: ctx.rateLimits.signIn } }, async (req, reply) => {
|
|
138
|
+
const token = req.cookies[OIDC_FLOW_COOKIE];
|
|
139
|
+
reply.clearCookie(OIDC_FLOW_COOKIE, { path: OIDC_FLOW_PATH });
|
|
140
|
+
const query = new URL(req.url, 'http://localhost').searchParams;
|
|
141
|
+
let flow = null;
|
|
142
|
+
try {
|
|
143
|
+
if (token)
|
|
144
|
+
flow = app.jwt.verify(token);
|
|
145
|
+
}
|
|
146
|
+
catch { /* expired or tampered: handled as missing */ }
|
|
147
|
+
if (flow?.purpose === 'oidc-test' && Number.isInteger(flow.adminUserId)) {
|
|
148
|
+
const cfg = await getOidcTestConfig();
|
|
149
|
+
if (!cfg)
|
|
150
|
+
return reply.code(404).send({ error: 'OIDC is not configured' });
|
|
151
|
+
const result = await runOidcTest(cfg, callbackUrlFor(cfg, query), flow);
|
|
152
|
+
return reply.redirect(`${ctx.urls.ssoTest}?result=${storeOidcTestResult(flow.adminUserId, result)}`);
|
|
153
|
+
}
|
|
154
|
+
const { config: cfg } = await getOidcConfig();
|
|
155
|
+
if (!cfg)
|
|
156
|
+
return reply.code(404).send({ error: 'OIDC is not configured' });
|
|
157
|
+
if (flow?.purpose === 'oidc-confirm') {
|
|
158
|
+
if (!flow.sessionId || !Number.isInteger(flow.userId))
|
|
159
|
+
return confirmRedirect(reply, false);
|
|
160
|
+
try {
|
|
161
|
+
const exchanged = await exchangeOidcCode(cfg, callbackUrlFor(cfg, query), flow, OIDC_CONFIRMATION_MAX_AGE_SECONDS);
|
|
162
|
+
const decision = exchanged.denial ? { user: null, denial: exchanged.denial } : await evaluateOidcClaims(cfg, exchanged.claims);
|
|
163
|
+
if (!decision.user || decision.user.id !== flow.userId) {
|
|
164
|
+
const denial = decision.denial ?? {
|
|
165
|
+
code: 'confirmation_mismatch',
|
|
166
|
+
reason: `The confirmation signed in as ${decision.user.email}, not as the user of the session being confirmed.`,
|
|
167
|
+
email: decision.user.email,
|
|
168
|
+
};
|
|
169
|
+
req.log.warn({ code: denial.code, reason: denial.reason }, 'OIDC confirmation refused');
|
|
170
|
+
await auditOidcDenial(cfg, denial);
|
|
171
|
+
return confirmRedirect(reply, false);
|
|
172
|
+
}
|
|
173
|
+
if (decision.link)
|
|
174
|
+
await linkOidcIdentity(cfg, decision.user, decision.subject);
|
|
175
|
+
return confirmRedirect(reply, await markSessionVerified(flow.sessionId, flow.userId));
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
req.log.warn({ err: error }, 'OIDC confirmation failed');
|
|
179
|
+
await auditOidcDenial(cfg, { code: 'token_exchange_failed', reason: `The confirmation could not be completed: ${errorText(error)}` });
|
|
180
|
+
return confirmRedirect(reply, false);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (flow?.purpose !== 'oidc-flow') {
|
|
184
|
+
// Only audited for what looks like a real provider redirect: anyone can open the callback URL.
|
|
185
|
+
if (query.has('state')) {
|
|
186
|
+
await auditOidcDenial(cfg, {
|
|
187
|
+
code: 'flow_expired',
|
|
188
|
+
reason: 'The sign-in session cookie was missing or expired. The sign-in took longer than 10 minutes, '
|
|
189
|
+
+ 'or the redirect URI points to a different host than the one the sign-in started on (for example localhost vs 127.0.0.1).',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return reply.redirect(OIDC_FAILED_REDIRECT);
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const exchanged = await exchangeOidcCode(cfg, callbackUrlFor(cfg, query), flow);
|
|
196
|
+
const decision = exchanged.denial ? { user: null, denial: exchanged.denial } : await evaluateOidcClaims(cfg, exchanged.claims);
|
|
197
|
+
let user;
|
|
198
|
+
if (decision.user) {
|
|
199
|
+
user = decision.user;
|
|
200
|
+
if (decision.link)
|
|
201
|
+
await linkOidcIdentity(cfg, user, decision.subject);
|
|
202
|
+
}
|
|
203
|
+
else if (decision.provision) {
|
|
204
|
+
user = await provisionSsoUser(cfg, decision.provision);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const { denial } = decision;
|
|
208
|
+
req.log.warn({ code: denial.code, reason: denial.reason }, 'OIDC sign-in refused');
|
|
209
|
+
await auditOidcDenial(cfg, denial);
|
|
210
|
+
return reply.redirect(signInRedirect(flow, { error: ACCOUNT_DENIALS.has(denial.code) ? 'oidc_no_account' : 'oidc_failed' }));
|
|
211
|
+
}
|
|
212
|
+
// The IdP replaces the password. A user who enabled 2FA here still has to enter their code.
|
|
213
|
+
if (user.totpEnabled) {
|
|
214
|
+
const challenge = { purpose: 'two-factor-login', userId: user.id, authMethod: 'oidc' };
|
|
215
|
+
reply.setCookie(TWO_FACTOR_CHALLENGE_COOKIE, app.jwt.sign(challenge, { expiresIn: TWO_FACTOR_CHALLENGE_SECONDS }), {
|
|
216
|
+
path: TWO_FACTOR_CHALLENGE_PATH,
|
|
217
|
+
httpOnly: true,
|
|
218
|
+
secure: ctx.secureCookies(),
|
|
219
|
+
sameSite: 'strict',
|
|
220
|
+
maxAge: TWO_FACTOR_CHALLENGE_SECONDS,
|
|
221
|
+
});
|
|
222
|
+
return reply.redirect(signInRedirect(flow, 'two-factor'));
|
|
223
|
+
}
|
|
224
|
+
const identity = await createAuthSession(app, reply, await revokeTemporaryPassword(user), 'oidc');
|
|
225
|
+
await auditSignIn(user, 'sso', false);
|
|
226
|
+
return reply.redirect(signInRedirect(flow, identity));
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
req.log.warn({ err: error }, 'OIDC callback failed');
|
|
230
|
+
await auditOidcDenial(cfg, { code: 'token_exchange_failed', reason: `The sign-in could not be completed: ${errorText(error)}` });
|
|
231
|
+
return reply.redirect(signInRedirect(flow, { error: 'oidc_failed' }));
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
app.post('/login', {
|
|
235
|
+
config: { rateLimit: ctx.rateLimits.signIn },
|
|
236
|
+
}, async (req, reply) => {
|
|
237
|
+
const { email, password } = req.body;
|
|
238
|
+
const typedEmail = typeof email === 'string' && email.trim() ? email.trim().slice(0, 320) : undefined;
|
|
239
|
+
if (!passwordLoginEnabled((await getOidcConfig()).config)) {
|
|
240
|
+
await denySignIn('password', 'password_login_disabled', 'Password sign-in is disabled; only single sign-on is allowed.', typedEmail);
|
|
241
|
+
return reply.code(403).send({ error: 'Password sign-in is disabled' });
|
|
242
|
+
}
|
|
243
|
+
const user = (await db.select().from(users).where(eq(users.email, email)))[0];
|
|
244
|
+
if (!user) {
|
|
245
|
+
await denySignIn('password', 'no_matching_account', `No user has the email ${typedEmail ?? '(none entered)'}.`, typedEmail);
|
|
246
|
+
return reply.code(401).send({ error: 'Invalid credentials' });
|
|
247
|
+
}
|
|
248
|
+
if (!await verifyPassword(user, password)) {
|
|
249
|
+
await denySignIn('password', 'wrong_password', 'The password is incorrect.', user.email);
|
|
250
|
+
return reply.code(401).send({ error: 'Invalid credentials' });
|
|
251
|
+
}
|
|
252
|
+
if (user.totpEnabled) {
|
|
253
|
+
const challengeToken = app.jwt.sign({ purpose: 'two-factor-login', userId: user.id }, { expiresIn: '5m' });
|
|
254
|
+
return { requiresTwoFactor: true, challengeToken };
|
|
255
|
+
}
|
|
256
|
+
const session = await finishLogin(app, reply, user);
|
|
257
|
+
await auditSignIn(user, 'password', false);
|
|
258
|
+
return session;
|
|
259
|
+
});
|
|
260
|
+
// A password sign-in sends its challenge in the body; an SSO sign-in left it in a cookie at the callback.
|
|
261
|
+
app.post('/2fa/verify', {
|
|
262
|
+
config: { rateLimit: ctx.rateLimits.signIn },
|
|
263
|
+
}, async (req, reply) => {
|
|
264
|
+
const token = req.body.challengeToken || req.cookies[TWO_FACTOR_CHALLENGE_COOKIE];
|
|
265
|
+
// Where the challenge came from says which sign-in it continues, even when it can no longer be read.
|
|
266
|
+
const method = req.body.challengeToken ? 'password' : 'sso';
|
|
267
|
+
let challenge;
|
|
268
|
+
try {
|
|
269
|
+
challenge = app.jwt.verify(token ?? '');
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
await denySignIn(method, 'two_factor_expired', 'The authentication code was entered more than 5 minutes after the first sign-in step, or the challenge was missing or invalid.');
|
|
273
|
+
return reply.code(401).send({ error: 'Two-factor challenge expired' });
|
|
274
|
+
}
|
|
275
|
+
if (challenge.purpose !== 'two-factor-login' || !Number.isInteger(challenge.userId)) {
|
|
276
|
+
return reply.code(401).send({ error: 'Invalid two-factor challenge' });
|
|
277
|
+
}
|
|
278
|
+
const found = (await db.select().from(users).where(eq(users.id, challenge.userId)))[0];
|
|
279
|
+
if (!found || !await verifySecondFactor(found, req.body.code)) {
|
|
280
|
+
if (found)
|
|
281
|
+
await denySignIn(method, 'invalid_two_factor_code', 'The authentication or recovery code is incorrect.', found.email);
|
|
282
|
+
return reply.code(401).send({ error: 'Invalid authentication code' });
|
|
283
|
+
}
|
|
284
|
+
reply.clearCookie(TWO_FACTOR_CHALLENGE_COOKIE, { path: TWO_FACTOR_CHALLENGE_PATH });
|
|
285
|
+
const authMethod = challenge.authMethod === 'oidc' ? 'oidc' : 'password';
|
|
286
|
+
const user = authMethod === 'oidc' ? await revokeTemporaryPassword(found) : found;
|
|
287
|
+
const session = await finishLogin(app, reply, user, authMethod);
|
|
288
|
+
await auditSignIn(user, authMethod === 'oidc' ? 'sso' : 'password', true);
|
|
289
|
+
return session;
|
|
290
|
+
});
|
|
291
|
+
app.get('/session', { preHandler: requireAuth, config: ALLOW_PENDING_PASSWORD_CHANGE }, async (req) => {
|
|
292
|
+
return publicSession(requestIdentity(req));
|
|
293
|
+
});
|
|
294
|
+
app.post('/logout', { preHandler: requireAuth, config: ALLOW_PENDING_PASSWORD_CHANGE }, async (req, reply) => {
|
|
295
|
+
const identity = requestIdentity(req);
|
|
296
|
+
await revokeSession(identity.sessionId);
|
|
297
|
+
clearAuthCookies(reply);
|
|
298
|
+
return reply.code(204).send();
|
|
299
|
+
});
|
|
300
|
+
app.post('/logout-all', { preHandler: requireAuth, config: ALLOW_PENDING_PASSWORD_CHANGE }, async (req, reply) => {
|
|
301
|
+
const identity = requestIdentity(req);
|
|
302
|
+
await revokeUserSessions(identity.userId);
|
|
303
|
+
clearAuthCookies(reply);
|
|
304
|
+
return reply.code(204).send();
|
|
305
|
+
});
|
|
306
|
+
app.post('/change-password', {
|
|
307
|
+
preHandler: requireAuth,
|
|
308
|
+
config: ALLOW_PENDING_PASSWORD_CHANGE,
|
|
309
|
+
}, async (req, reply) => {
|
|
310
|
+
const { newPassword, currentPassword } = req.body;
|
|
311
|
+
if (!newPassword || newPassword.length < 8 || newPassword.length > 128) {
|
|
312
|
+
return reply.code(400).send({ error: 'Password must be between 8 and 128 characters' });
|
|
313
|
+
}
|
|
314
|
+
const identity = requestIdentity(req);
|
|
315
|
+
const user = (await db.select().from(users).where(eq(users.id, identity.userId)))[0];
|
|
316
|
+
if (!user)
|
|
317
|
+
return reply.code(404).send({ error: 'User not found' });
|
|
318
|
+
// The session, not the user row: an SSO session never has to replace a temporary password, so it confirms too.
|
|
319
|
+
if (!identity.mustChangePassword) {
|
|
320
|
+
if (identity.authMethod === 'password' && !currentPassword)
|
|
321
|
+
return reply.code(400).send({ error: 'Current password is required' });
|
|
322
|
+
if (!await confirmIdentity(identity, currentPassword, reply))
|
|
323
|
+
return reply;
|
|
324
|
+
}
|
|
325
|
+
const passwordHash = await bcrypt.hash(newPassword, 10);
|
|
326
|
+
await db.update(users).set({ passwordHash, mustChangePassword: 0 }).where(eq(users.id, user.id));
|
|
327
|
+
await audit.write({ userId: identity.userId, userEmail: identity.email }, 'update', 'user-security', identity.userId, identity.email, { passwordChanged: true });
|
|
328
|
+
await revokeUserSessions(user.id);
|
|
329
|
+
const updated = { ...user, passwordHash, mustChangePassword: 0 };
|
|
330
|
+
// An SSO user setting a password stays in an SSO session.
|
|
331
|
+
return finishLogin(app, reply, updated, identity.authMethod);
|
|
332
|
+
});
|
|
333
|
+
app.post('/2fa/setup', { preHandler: requireAuth }, async (req, reply) => {
|
|
334
|
+
const identity = requestIdentity(req);
|
|
335
|
+
if (!await confirmIdentity(identity, req.body?.currentPassword, reply))
|
|
336
|
+
return reply;
|
|
337
|
+
const user = (await db.select().from(users).where(eq(users.id, identity.userId)))[0];
|
|
338
|
+
if (!user)
|
|
339
|
+
return reply.code(404).send({ error: 'User not found' });
|
|
340
|
+
if (user.totpEnabled)
|
|
341
|
+
return reply.code(409).send({ error: 'Two-factor authentication is already enabled' });
|
|
342
|
+
const secret = generateTotpSecret();
|
|
343
|
+
const uri = totpUri(secret, user.email, totpIssuer);
|
|
344
|
+
const setupToken = app.jwt.sign({
|
|
345
|
+
purpose: 'two-factor-setup',
|
|
346
|
+
userId: user.id,
|
|
347
|
+
encryptedSecret: secretBox.encrypt(secret),
|
|
348
|
+
}, { expiresIn: '10m' });
|
|
349
|
+
const qrDataUrl = await QRCode.toDataURL(uri, {
|
|
350
|
+
errorCorrectionLevel: 'M',
|
|
351
|
+
margin: 1,
|
|
352
|
+
width: 240,
|
|
353
|
+
color: { dark: '#111827', light: '#ffffff' },
|
|
354
|
+
});
|
|
355
|
+
return { secret, uri, qrDataUrl, setupToken };
|
|
356
|
+
});
|
|
357
|
+
app.post('/2fa/enable', { preHandler: requireAuth }, async (req, reply) => {
|
|
358
|
+
const identity = requestIdentity(req);
|
|
359
|
+
let setup;
|
|
360
|
+
try {
|
|
361
|
+
setup = app.jwt.verify(req.body.setupToken);
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
return reply.code(400).send({ error: 'Two-factor setup expired' });
|
|
365
|
+
}
|
|
366
|
+
if (setup.purpose !== 'two-factor-setup' || setup.userId !== identity.userId || !setup.encryptedSecret) {
|
|
367
|
+
return reply.code(400).send({ error: 'Invalid two-factor setup' });
|
|
368
|
+
}
|
|
369
|
+
const secret = secretBox.decrypt(setup.encryptedSecret);
|
|
370
|
+
if (!verifyTotp(secret, req.body.code)) {
|
|
371
|
+
return reply.code(400).send({ error: 'Invalid authentication code' });
|
|
372
|
+
}
|
|
373
|
+
const recoveryCodes = generateRecoveryCodes();
|
|
374
|
+
await db.update(users).set({
|
|
375
|
+
totpSecret: secretBox.encrypt(secret),
|
|
376
|
+
totpEnabled: 1,
|
|
377
|
+
totpRecoveryCodes: JSON.stringify(recoveryCodes.map(hashRecoveryCode)),
|
|
378
|
+
}).where(eq(users.id, identity.userId));
|
|
379
|
+
await revokeUserSessions(identity.userId, identity.sessionId);
|
|
380
|
+
await audit.write({ userId: identity.userId, userEmail: identity.email }, 'update', 'user-security', identity.userId, identity.email, { twoFactorEnabled: { from: false, to: true } });
|
|
381
|
+
return { recoveryCodes };
|
|
382
|
+
});
|
|
383
|
+
app.post('/2fa/disable', { preHandler: requireAuth }, async (req, reply) => {
|
|
384
|
+
const identity = requestIdentity(req);
|
|
385
|
+
if (!await confirmIdentity(identity, req.body?.currentPassword, reply))
|
|
386
|
+
return reply;
|
|
387
|
+
const user = (await db.select().from(users).where(eq(users.id, identity.userId)))[0];
|
|
388
|
+
if (!user)
|
|
389
|
+
return reply.code(404).send({ error: 'User not found' });
|
|
390
|
+
if (!user.totpEnabled)
|
|
391
|
+
return reply.code(409).send({ error: 'Two-factor authentication is not enabled' });
|
|
392
|
+
if (!await verifySecondFactor(user, req.body.code)) {
|
|
393
|
+
return reply.code(400).send({ error: 'Invalid authentication code' });
|
|
394
|
+
}
|
|
395
|
+
await db.update(users).set({ totpSecret: null, totpEnabled: 0, totpRecoveryCodes: null }).where(eq(users.id, user.id));
|
|
396
|
+
await revokeUserSessions(user.id, identity.sessionId);
|
|
397
|
+
await audit.write({ userId: identity.userId, userEmail: identity.email }, 'update', 'user-security', identity.userId, identity.email, { twoFactorEnabled: { from: true, to: false } });
|
|
398
|
+
return { twoFactorEnabled: false };
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
return { startOidcFlow, authRoutes };
|
|
402
|
+
}
|
|
403
|
+
//# sourceMappingURL=authRoutes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authRoutes.js","sourceRoot":"","sources":["../src/authRoutes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { Audit } from '@better-forge/audit';
|
|
3
|
+
import type { SqliteDatabase } from '@better-forge/db';
|
|
4
|
+
import type { RoleRegistry } from './roles.js';
|
|
5
|
+
import type { TokenCatalogue } from './tokenCatalogue.js';
|
|
6
|
+
/** Which sessions were ended, so the application can close what they had open (for example live connections). */
|
|
7
|
+
export type SessionsRevoked = {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
} | {
|
|
10
|
+
userId: number;
|
|
11
|
+
exceptSessionId?: string | undefined;
|
|
12
|
+
};
|
|
13
|
+
export interface UnknownSsoUser {
|
|
14
|
+
email: string;
|
|
15
|
+
subject: string;
|
|
16
|
+
issuer: string;
|
|
17
|
+
}
|
|
18
|
+
export type SignInOutcome = {
|
|
19
|
+
role: string;
|
|
20
|
+
} | {
|
|
21
|
+
error: string;
|
|
22
|
+
} | 'two-factor';
|
|
23
|
+
export interface AuthHooks {
|
|
24
|
+
/** Called after sessions were deleted, whatever the reason (sign-out, password reset, role change, ...). */
|
|
25
|
+
onSessionsRevoked?: (match: SessionsRevoked) => void;
|
|
26
|
+
/**
|
|
27
|
+
* An SSO sign-in succeeded at the provider but no account matches. Return the role of an account to create for
|
|
28
|
+
* this person, or null to refuse. Only called when the provider's identity is verified; without this hook nobody
|
|
29
|
+
* is created.
|
|
30
|
+
*/
|
|
31
|
+
resolveUnknownSsoUser?: (user: UnknownSsoUser) => Promise<{
|
|
32
|
+
role: string;
|
|
33
|
+
} | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Where a sign-in goes next, for one that started with a `returnTo` target the application listed in
|
|
36
|
+
* `returnTargets`, or for a role that belongs somewhere else than `urls.home`. Return undefined for the default.
|
|
37
|
+
*/
|
|
38
|
+
signInRedirect?: (flow: {
|
|
39
|
+
returnTo?: string | undefined;
|
|
40
|
+
}, outcome: SignInOutcome) => string | undefined;
|
|
41
|
+
}
|
|
42
|
+
/** A `@fastify/rate-limit` route configuration. Passed through untouched, so a group id can be shared with other routes. */
|
|
43
|
+
export interface RateLimitConfig {
|
|
44
|
+
groupId?: string;
|
|
45
|
+
max: number;
|
|
46
|
+
timeWindow: string | number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Everything the auth module needs from the application. All of it is passed in: the module reads no globals, so
|
|
50
|
+
* two applications (or two tests) can each have their own.
|
|
51
|
+
*/
|
|
52
|
+
export interface AuthOptions {
|
|
53
|
+
/** Database handle and the raw connection (for transactions). Both are used lazily, so they may be opened after this is created. */
|
|
54
|
+
db: SqliteDatabase;
|
|
55
|
+
sqlite: DatabaseSync;
|
|
56
|
+
audit: Pick<Audit, 'write' | 'actor' | 'diffObjects' | 'snapshot'>;
|
|
57
|
+
roles: RoleRegistry;
|
|
58
|
+
tokens: TokenCatalogue;
|
|
59
|
+
/** Encrypts and decrypts secrets the module stores (the SSO client secret, authenticator secrets). */
|
|
60
|
+
secretBox: {
|
|
61
|
+
encrypt(plain: string): string;
|
|
62
|
+
decrypt(stored: string): string;
|
|
63
|
+
};
|
|
64
|
+
cookies: {
|
|
65
|
+
session: string;
|
|
66
|
+
csrf: string;
|
|
67
|
+
oidcFlow: string;
|
|
68
|
+
twoFactorChallenge: string;
|
|
69
|
+
};
|
|
70
|
+
/** Prefix that tells an API token from a session token in a Bearer header. */
|
|
71
|
+
apiTokenPrefix: string;
|
|
72
|
+
/** Name an authenticator app shows next to the account. */
|
|
73
|
+
totpIssuer: string;
|
|
74
|
+
/** Where the sign-in routes are mounted. The cookie paths and the SSO callback derive from it. */
|
|
75
|
+
basePath: string;
|
|
76
|
+
urls: {
|
|
77
|
+
/** Where a failed sign-in sends the browser, e.g. `/admin/login`. */
|
|
78
|
+
signIn: string;
|
|
79
|
+
/** Where a finished sign-in goes by default. */
|
|
80
|
+
home: string;
|
|
81
|
+
/** The page that closes the SSO confirmation popup. */
|
|
82
|
+
ssoConfirm: string;
|
|
83
|
+
/** The page that shows the result of an SSO test. */
|
|
84
|
+
ssoTest: string;
|
|
85
|
+
};
|
|
86
|
+
/** The address the application is reached at, from the environment; '' when unknown. */
|
|
87
|
+
publicUrl: (env: NodeJS.ProcessEnv) => string;
|
|
88
|
+
/** Whether cookies are marked Secure. Evaluated on every response. */
|
|
89
|
+
secureCookies: () => boolean;
|
|
90
|
+
rateLimits: {
|
|
91
|
+
signIn: RateLimitConfig;
|
|
92
|
+
sensitiveAction: RateLimitConfig;
|
|
93
|
+
};
|
|
94
|
+
/** `returnTo` values a sign-in may start with; the application's `signInRedirect` hook turns them into a destination. */
|
|
95
|
+
returnTargets?: readonly string[];
|
|
96
|
+
hooks?: AuthHooks;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":""}
|