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:
+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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user