fix(security): consolidation & securite phase 1 (#84, BUG-021 a BUG-034)
- sanitizer XSS serveur (markdown + page de partage) [BUG-021/022] - rate-limit/lockout MFA [BUG-023] - isolation vaults par segments [BUG-024] - caps regex ReDoS [BUG-025] - SSRF webhooks + secrets externalises [BUG-026] - rotation/revocation des jetons [BUG-027] - politique de mot de passe + invalidation sessions [BUG-028] - verrous users.json [BUG-029] - IP reelle dans les audits [BUG-030] - rate-limit par compte [BUG-031] - symlinks hors vault ignores [BUG-032] - recherche simple via inverted index [BUG-033] - token en memoire + cookie HttpOnly, CSP durcie [BUG-034] Tests: pytest 961 passed / 6 skipped, ruff 0, mypy 0, frontend vert.
This commit is contained in:
@@ -28,17 +28,12 @@ let _auditFilters = { user: "", action: "" };
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Read the current Bearer token from sessionStorage (same key auth.js uses).
|
||||
* Returns an `{Authorization: "Bearer ..."}` object or null.
|
||||
* BUG-034: the access token now lives in an HttpOnly cookie (and in memory
|
||||
* inside auth.js), never in sessionStorage. Same-origin requests carry the
|
||||
* cookie automatically, so no Authorization header is needed here.
|
||||
*/
|
||||
export function getAuthHeaders() {
|
||||
try {
|
||||
const token = sessionStorage.getItem("obsigate_access_token");
|
||||
if (!token) return null;
|
||||
return { Authorization: "Bearer " + token };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Format a size in MB as a human-readable string. */
|
||||
|
||||
+18
-6
@@ -85,12 +85,18 @@ const AuthManager = {
|
||||
TOKEN_EXPIRY_KEY: "obsigate_token_expiry",
|
||||
USER_KEY: "obsigate_user",
|
||||
_authEnabled: false,
|
||||
// BUG-034: the access token is kept in memory only. The server also sets it
|
||||
// as an HttpOnly cookie, so a page reload re-authenticates via /api/auth/refresh
|
||||
// without ever exposing the token to JavaScript-readable storage.
|
||||
_accessToken: null,
|
||||
|
||||
// ── Token storage (sessionStorage) ─────────────────────────────
|
||||
// ── Token storage (in-memory + HttpOnly cookie) ────────────────
|
||||
|
||||
saveToken(tokenData) {
|
||||
const expiresAt = Date.now() + tokenData.expires_in * 1000;
|
||||
sessionStorage.setItem(this.ACCESS_TOKEN_KEY, tokenData.access_token);
|
||||
this._accessToken = tokenData.access_token;
|
||||
// Clear any token persisted by an older build (XSS-readable).
|
||||
try { sessionStorage.removeItem(this.ACCESS_TOKEN_KEY); } catch (e) { /* ignore */ }
|
||||
sessionStorage.setItem(this.TOKEN_EXPIRY_KEY, expiresAt.toString());
|
||||
if (tokenData.user) {
|
||||
sessionStorage.setItem(this.USER_KEY, JSON.stringify(tokenData.user));
|
||||
@@ -98,7 +104,11 @@ const AuthManager = {
|
||||
},
|
||||
|
||||
getToken() {
|
||||
return sessionStorage.getItem(this.ACCESS_TOKEN_KEY);
|
||||
return this._accessToken;
|
||||
},
|
||||
|
||||
hasSession() {
|
||||
return !!this._accessToken || !!sessionStorage.getItem(this.TOKEN_EXPIRY_KEY);
|
||||
},
|
||||
|
||||
getUser() {
|
||||
@@ -114,7 +124,8 @@ const AuthManager = {
|
||||
},
|
||||
|
||||
clearSession() {
|
||||
sessionStorage.removeItem(this.ACCESS_TOKEN_KEY);
|
||||
this._accessToken = null;
|
||||
try { sessionStorage.removeItem(this.ACCESS_TOKEN_KEY); } catch (e) { /* ignore */ }
|
||||
sessionStorage.removeItem(this.TOKEN_EXPIRY_KEY);
|
||||
sessionStorage.removeItem(this.USER_KEY);
|
||||
},
|
||||
@@ -281,7 +292,8 @@ const AuthManager = {
|
||||
}
|
||||
const data = await response.json();
|
||||
const expiry = Date.now() + data.expires_in * 1000;
|
||||
sessionStorage.setItem(this.ACCESS_TOKEN_KEY, data.access_token);
|
||||
this._accessToken = data.access_token;
|
||||
try { sessionStorage.removeItem(this.ACCESS_TOKEN_KEY); } catch (e) { /* ignore */ }
|
||||
sessionStorage.setItem(this.TOKEN_EXPIRY_KEY, expiry.toString());
|
||||
return data.access_token;
|
||||
},
|
||||
@@ -359,7 +371,7 @@ const AuthManager = {
|
||||
}
|
||||
|
||||
// Auth enabled — check for existing session
|
||||
if (this.getToken() && !this.isTokenExpired()) {
|
||||
if (this.hasSession() && !this.isTokenExpired()) {
|
||||
this.showApp();
|
||||
return true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -612,7 +612,7 @@ export function init() {
|
||||
// fresh login, so we only do it here for already-authenticated or
|
||||
// auth-disabled sessions.
|
||||
setTimeout(() => {
|
||||
if (!AuthManager._authEnabled || AuthManager.getToken()) {
|
||||
if (!AuthManager._authEnabled || AuthManager.hasSession()) {
|
||||
syncFileIndexFromServer();
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
Reference in New Issue
Block a user