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:
parent
880d70d088
commit
061977126a
3 changed files with 19 additions and 12 deletions
|
|
@ -43,8 +43,8 @@
|
||||||
"last_deliberation": null
|
"last_deliberation": null
|
||||||
},
|
},
|
||||||
"FileEditHook": {
|
"FileEditHook": {
|
||||||
"timestamp": "2025-10-24T03:32:50.799Z",
|
"timestamp": "2025-10-24T03:42:14.478Z",
|
||||||
"file": "/home/theflow/projects/tractatus/public/js/admin/submission-modal-enhanced.js",
|
"file": "/home/theflow/projects/tractatus/src/middleware/csrf-protection.middleware.js",
|
||||||
"result": "passed"
|
"result": "passed"
|
||||||
},
|
},
|
||||||
"FileWriteHook": {
|
"FileWriteHook": {
|
||||||
|
|
@ -58,25 +58,25 @@
|
||||||
"tokens": 30000
|
"tokens": 30000
|
||||||
},
|
},
|
||||||
"alerts": [],
|
"alerts": [],
|
||||||
"last_updated": "2025-10-24T03:32:50.799Z",
|
"last_updated": "2025-10-24T03:42:14.478Z",
|
||||||
"initialized": true,
|
"initialized": true,
|
||||||
"framework_components": {
|
"framework_components": {
|
||||||
"CrossReferenceValidator": {
|
"CrossReferenceValidator": {
|
||||||
"message": 0,
|
"message": 0,
|
||||||
"tokens": 0,
|
"tokens": 0,
|
||||||
"timestamp": "2025-10-24T03:35:34.228Z",
|
"timestamp": "2025-10-24T03:42:14.476Z",
|
||||||
"last_validation": "2025-10-24T03:35:34.228Z",
|
"last_validation": "2025-10-24T03:42:14.476Z",
|
||||||
"validations_performed": 951
|
"validations_performed": 957
|
||||||
},
|
},
|
||||||
"BashCommandValidator": {
|
"BashCommandValidator": {
|
||||||
"message": 0,
|
"message": 0,
|
||||||
"tokens": 0,
|
"tokens": 0,
|
||||||
"timestamp": null,
|
"timestamp": null,
|
||||||
"last_validation": "2025-10-24T03:35:34.229Z",
|
"last_validation": "2025-10-24T03:42:26.291Z",
|
||||||
"validations_performed": 584,
|
"validations_performed": 589,
|
||||||
"blocks_issued": 62
|
"blocks_issued": 66
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"action_count": 584,
|
"action_count": 589,
|
||||||
"auto_compact_events": []
|
"auto_compact_events": []
|
||||||
}
|
}
|
||||||
|
|
@ -571,10 +571,17 @@ function setupNewsletterModal() {
|
||||||
submitBtn.textContent = 'Subscribing...';
|
submitBtn.textContent = 'Subscribing...';
|
||||||
|
|
||||||
try {
|
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', {
|
const response = await fetch('/api/newsletter/subscribe', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': csrfToken || ''
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email,
|
email,
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ function setCsrfToken(req, res, next) {
|
||||||
const isSecure = req.secure || req.headers['x-forwarded-proto'] === 'https';
|
const isSecure = req.secure || req.headers['x-forwarded-proto'] === 'https';
|
||||||
|
|
||||||
res.cookie('csrf-token', token, {
|
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',
|
secure: isSecure && process.env.NODE_ENV === 'production',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue