feat(setup): validate setup token at step 1 before advancing
Previously "Continue" on the token step only checked the field was non-empty; a wrong token wasn't caught until the final submit, after the user had filled in email + password. Add a non-burning verify: - backend: POST /setup/verify-token constant-time compares the token without consuming it (createInitialAdmin still claims it atomically on submit), gated on no-admin-exists and rate-limited like /setup/admin. - frontend: step-1 "Continue" calls verifyToken and only advances on a valid token; a wrong token shows the invalidToken error on the field, 429 -> too-many-attempts, 409 -> redirect to login. Adds integration tests for accept-without-burn / reject / closed-once-set.
This commit is contained in:
@@ -23,6 +23,31 @@ router.get('/status', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Step-1 pre-flight: validate the setup token without consuming it, so the
|
||||
// two-step wizard can block "Continue" on a wrong token. Rate-limited at the
|
||||
// mount point in server.js (authRateLimiter), same as POST /admin.
|
||||
router.post('/verify-token', [
|
||||
body('token').notEmpty().withMessage('Setup token is required'),
|
||||
], async (req, res) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const valid = await setupService.verifySetupToken(req.body.token);
|
||||
if (!valid) {
|
||||
return res.status(400).json({ error: 'Invalid setup token', field: 'token' });
|
||||
}
|
||||
return res.json({ valid: true });
|
||||
} catch (err) {
|
||||
if (err.statusCode) {
|
||||
return res.status(err.statusCode).json({ error: err.message, field: err.details || undefined });
|
||||
}
|
||||
logger.error('[setup] verifyToken failed', { error: err.message });
|
||||
return res.status(500).json({ error: 'Setup failed' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/admin', [
|
||||
body('token').notEmpty().withMessage('Setup token is required'),
|
||||
body('email').isEmail().withMessage('A valid email is required'),
|
||||
|
||||
Reference in New Issue
Block a user