diff --git a/backend/utils/app.js b/backend/utils/app.js index 7ca996a8..8cd5e096 100644 --- a/backend/utils/app.js +++ b/backend/utils/app.js @@ -46,12 +46,12 @@ export class ExpressApplication { res.sendFile(join(this.directoryName, '../..', 'frontend', 'index.html')); }); this.app.use((err, req, res, next) => { - this.handleApplicationErrors(err, res); + this.handleApplicationErrors(err, req, res); next(); }); this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'App', msg: 'Application Routes Set' }); }; - this.handleApplicationErrors = (err, res) => { + this.handleApplicationErrors = (err, req, res) => { switch (err.code) { case 'EACCES': this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'Server requires elevated privileges' }); @@ -66,6 +66,16 @@ export class ExpressApplication { res.status(401).send('Server is down/locked.'); break; case 'EBADCSRFTOKEN': + // Re-mint the token for the current session so a client retry succeeds + // (the stale one may be bound to a destroyed session or rotated secret). + try { + const csrfToken = CSRF.reMintToken(req, res); + res.cookie('XSRF-TOKEN', csrfToken); + res.setHeader('XSRF-TOKEN', csrfToken); + } + catch (csrfError) { + this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'CSRF Token Re-Mint Failed', error: csrfError }); + } this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'Invalid CSRF token. Form tempered.' }); res.status(403).send('Invalid CSRF token, form tempered.'); break; diff --git a/backend/utils/csrf.js b/backend/utils/csrf.js index 0a48cd6d..0f98607d 100644 --- a/backend/utils/csrf.js +++ b/backend/utils/csrf.js @@ -21,6 +21,10 @@ class CSRF { req.headers['x-csrf-token'] || req.headers['x-xsrf-token'] }); this.csrfProtection = this.doubleCsrfUtilities.doubleCsrfProtection; + // Force-mints a fresh token for the current session, discarding any token + // cookie bound to a previous session or boot secret (used by the + // EBADCSRFTOKEN error path in app.ts so a client retry succeeds). + this.reMintToken = (req, res) => this.doubleCsrfUtilities.generateCsrfToken(req, res, { overwrite: true }); } mount(app) { this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'CSRF', msg: 'Setting up CSRF..' }); diff --git a/frontend/index.html b/frontend/index.html index db0b1c8a..13fbd5c1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -15,5 +15,5 @@