reopening the MFA setup screen while MFA is enabled no longer wipes the secret and backup codes
This commit is contained in:
2 files changed
+20
-1
No files matched your search
@@ -937,10 +937,16 @@ function parseMfaData(mfaSecret: string | null | undefined): MfaData | null {
|
||||
export async function generateMfaSetup(userId: number): Promise<{
|
||||
secret: string;
|
||||
qrDataUrl: string;
|
||||
} | null> {
|
||||
} | { alreadyEnabled: true } | null> {
|
||||
const user = await getUser(userId);
|
||||
if (!user) return null;
|
||||
|
||||
// Never regenerate over a LIVE enrolment. Overwriting the stored secret + clearing
|
||||
// backupCodes while mfaEnabled stays true locks the account out for good (#1399): the
|
||||
// authenticator holds the old secret, no backup code can match, and login keeps demanding
|
||||
// a code. To re-enrol, the user must disable MFA first (DELETE), then set it up fresh.
|
||||
if (user.mfaEnabled) return { alreadyEnabled: true };
|
||||
|
||||
// Build issuer name with hostname (same as license matching)
|
||||
const hostname = process.env.DOCKHAND_HOSTNAME || os.hostname();
|
||||
const issuer = `Dockhand (${hostname})`;
|
||||
|
||||
@@ -55,11 +55,24 @@ export const POST: RequestHandler = async (event) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Only an explicit setup request regenerates. Treating "anything not verify" as
|
||||
// "regenerate" let a stray/empty POST (a restored tab, a refresh) destroy a live MFA
|
||||
// enrolment (#1399). An empty body still means setup for backward-compat with the current
|
||||
// client, but a body with a DIFFERENT action is rejected rather than silently regenerating.
|
||||
if (body.action && body.action !== 'setup') {
|
||||
return json({ error: `Unknown MFA action "${body.action}"` }, { status: 400 });
|
||||
}
|
||||
|
||||
// Generate new MFA setup
|
||||
const setup = await generateMfaSetup(userId);
|
||||
if (!setup) {
|
||||
return json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
if ('alreadyEnabled' in setup) {
|
||||
// Refuse to overwrite a live enrolment - would lock the user out (#1399). They must
|
||||
// disable MFA first, then set it up again.
|
||||
return json({ error: 'MFA is already enabled. Disable it before setting it up again.' }, { status: 409 });
|
||||
}
|
||||
|
||||
return json({
|
||||
secret: setup.secret,
|
||||
|
||||
Reference in new issue
Block a user