fix(csrf): enable newsletter subscription from mobile

CRITICAL FIX: Newsletter subscription was returning "Forbidden" error
because the CSRF protection was incorrectly configured.

Root cause:
- CSRF cookie was set with httpOnly: true
- JavaScript cannot read httpOnly cookies
- Frontend couldn't extract token to send in X-CSRF-Token header
- Double-submit CSRF pattern requires client to read the cookie

Changes:
- csrf-protection.middleware.js: Set httpOnly: false (required for double-submit pattern)
- blog.js: Extract CSRF token from cookie and include in X-CSRF-Token header

Security Note: This is the correct implementation per OWASP guidelines
for double-submit cookie CSRF protection. The cookie is still protected
by SameSite: strict and domain restrictions.

Fixes: #newsletter-subscription-forbidden-mobile
This commit is contained in:
TheFlow 2025-10-24 16:42:56 +13:00
parent 880d70d088
commit 061977126a
3 changed files with 19 additions and 12 deletions

View file

@ -43,8 +43,8 @@
"last_deliberation": null
},
"FileEditHook": {
"timestamp": "2025-10-24T03:32:50.799Z",
"file": "/home/theflow/projects/tractatus/public/js/admin/submission-modal-enhanced.js",
"timestamp": "2025-10-24T03:42:14.478Z",
"file": "/home/theflow/projects/tractatus/src/middleware/csrf-protection.middleware.js",
"result": "passed"
},
"FileWriteHook": {
@ -58,25 +58,25 @@
"tokens": 30000
},
"alerts": [],
"last_updated": "2025-10-24T03:32:50.799Z",
"last_updated": "2025-10-24T03:42:14.478Z",
"initialized": true,
"framework_components": {
"CrossReferenceValidator": {
"message": 0,
"tokens": 0,
"timestamp": "2025-10-24T03:35:34.228Z",
"last_validation": "2025-10-24T03:35:34.228Z",
"validations_performed": 951
"timestamp": "2025-10-24T03:42:14.476Z",
"last_validation": "2025-10-24T03:42:14.476Z",
"validations_performed": 957
},
"BashCommandValidator": {
"message": 0,
"tokens": 0,
"timestamp": null,
"last_validation": "2025-10-24T03:35:34.229Z",
"validations_performed": 584,
"blocks_issued": 62
"last_validation": "2025-10-24T03:42:26.291Z",
"validations_performed": 589,
"blocks_issued": 66
}
},
"action_count": 584,
"action_count": 589,
"auto_compact_events": []
}

View file

@ -571,10 +571,17 @@ function setupNewsletterModal() {
submitBtn.textContent = 'Subscribing...';
try {
// Get CSRF token from cookie
const csrfToken = document.cookie
.split('; ')
.find(row => row.startsWith('csrf-token='))
?.split('=')[1];
const response = await fetch('/api/newsletter/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken || ''
},
body: JSON.stringify({
email,

View file

@ -79,7 +79,7 @@ function setCsrfToken(req, res, next) {
const isSecure = req.secure || req.headers['x-forwarded-proto'] === 'https';
res.cookie('csrf-token', token, {
httpOnly: true,
httpOnly: false, // Must be false for double-submit pattern (client needs to read it)
secure: isSecure && process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000 // 24 hours