feat(security): HttpOnly cookies + CSP headers + CSRF double-submit protection

- Migrate auth tokens from localStorage to HttpOnly SameSite=Lax cookies
- Add cookie_auth.rs: helpers for setting/clearing auth + CSRF cookies
- Update auth middleware: 3-method auth (Bearer → Basic → Cookie)
- Add 5 security headers: CSP, X-Content-Type-Options, X-Frame-Options,
  Referrer-Policy, Permissions-Policy
- Implement CSRF double-submit cookie pattern (csrf.rs middleware)
- Set CSRF cookie on login/refresh/oidc-exchange, clear on logout
- CookieAuthenticated marker skips CSRF for Bearer/Basic clients
- Frontend: strip all localStorage token refs from 14 JS files
- Frontend: csrf.js utility + all 52 mutating fetch/XHR calls protected
- 121 tests passing, 0 warnings
This commit is contained in:
Dionisio
2026-03-03 01:10:50 +01:00
parent 7b2a8577a9
commit d2c08d31ba
27 changed files with 579 additions and 455 deletions
+5 -18
View File
@@ -210,10 +210,7 @@ class InlineViewer {
try {
console.log('Creating text viewer for:', file.name);
const token = localStorage.getItem('oxicloud_token');
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
const response = await fetch(`/api/files/${file.id}?inline=true`, { headers });
const response = await fetch(`/api/files/${file.id}?inline=true`, { credentials: 'same-origin' });
if (!response.ok) {
throw new Error(`Error fetching file: ${response.status} ${response.statusText}`);
@@ -254,12 +251,7 @@ class InlineViewer {
const xhr = new XMLHttpRequest();
xhr.open('GET', `/api/files/${file.id}?inline=true`, true);
xhr.responseType = 'blob';
// Add auth header
const token = localStorage.getItem('oxicloud_token');
if (token) {
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
}
xhr.withCredentials = true;
// Create a promise to handle the XHR
const response = await new Promise((resolve, reject) => {
@@ -357,10 +349,8 @@ class InlineViewer {
try {
console.log(`Creating ${mediaType} player for:`, file.name);
// Fetch file with auth header (same pattern as images/PDFs)
const token = localStorage.getItem('oxicloud_token');
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
const response = await fetch(`/api/files/${file.id}?inline=true`, { headers });
// Fetch file (cookie auto-sent)
const response = await fetch(`/api/files/${file.id}?inline=true`, { credentials: 'same-origin' });
if (!response.ok) {
throw new Error(`Error fetching file: ${response.status} ${response.statusText}`);
@@ -488,10 +478,7 @@ class InlineViewer {
}
downloadFile(file) {
const token = localStorage.getItem('oxicloud_token');
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
fetch(`/api/files/${file.id}`, { headers })
fetch(`/api/files/${file.id}`, { credentials: 'same-origin' })
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.blob();