Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
222 lines
5.8 KiB
JavaScript
222 lines
5.8 KiB
JavaScript
/**
|
|
* CSRF Manager
|
|
*
|
|
* Handles CSRF token management including automatic refresh.
|
|
*/
|
|
|
|
import { Logger } from '../../core/logger.js';
|
|
|
|
/**
|
|
* CsrfManager - CSRF token management
|
|
*/
|
|
export class CsrfManager {
|
|
constructor(config = {}) {
|
|
this.config = {
|
|
tokenName: config.tokenName || '_token',
|
|
headerName: config.headerName || 'X-CSRF-TOKEN',
|
|
refreshInterval: config.refreshInterval || 30 * 60 * 1000, // 30 minutes
|
|
autoRefresh: config.autoRefresh ?? true,
|
|
endpoint: config.endpoint || '/api/csrf-token',
|
|
...config
|
|
};
|
|
|
|
this.currentToken = null;
|
|
this.refreshTimer = null;
|
|
this.isRefreshing = false;
|
|
|
|
// Initialize
|
|
this.init();
|
|
}
|
|
|
|
/**
|
|
* Create a new CsrfManager instance
|
|
*/
|
|
static create(config = {}) {
|
|
return new CsrfManager(config);
|
|
}
|
|
|
|
/**
|
|
* Initialize CSRF manager
|
|
*/
|
|
init() {
|
|
// Get initial token from meta tag or form
|
|
this.currentToken = this.getTokenFromPage();
|
|
|
|
if (!this.currentToken) {
|
|
Logger.warn('[CsrfManager] No CSRF token found on page');
|
|
} else {
|
|
Logger.info('[CsrfManager] Initialized with token');
|
|
}
|
|
|
|
// Set up auto-refresh if enabled
|
|
if (this.config.autoRefresh) {
|
|
this.startAutoRefresh();
|
|
}
|
|
|
|
// Update all forms and meta tags
|
|
this.updateAllTokens();
|
|
}
|
|
|
|
/**
|
|
* Get token from page (meta tag or form)
|
|
*/
|
|
getTokenFromPage() {
|
|
// Try meta tag first
|
|
const metaTag = document.querySelector('meta[name="csrf-token"]');
|
|
if (metaTag) {
|
|
return metaTag.getAttribute('content');
|
|
}
|
|
|
|
// Try form input
|
|
const formInput = document.querySelector(`input[name="${this.config.tokenName}"]`);
|
|
if (formInput) {
|
|
return formInput.value;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get current CSRF token
|
|
*/
|
|
getToken() {
|
|
return this.currentToken;
|
|
}
|
|
|
|
/**
|
|
* Refresh CSRF token
|
|
*/
|
|
async refreshToken() {
|
|
if (this.isRefreshing) {
|
|
Logger.debug('[CsrfManager] Token refresh already in progress');
|
|
return;
|
|
}
|
|
|
|
this.isRefreshing = true;
|
|
|
|
try {
|
|
const response = await fetch(this.config.endpoint, {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to refresh token: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
const newToken = data.token || data.csrf_token || data._token;
|
|
|
|
if (!newToken) {
|
|
throw new Error('No token in response');
|
|
}
|
|
|
|
this.currentToken = newToken;
|
|
this.updateAllTokens();
|
|
|
|
Logger.info('[CsrfManager] Token refreshed');
|
|
|
|
// Trigger event
|
|
this.triggerTokenRefreshedEvent(newToken);
|
|
|
|
} catch (error) {
|
|
Logger.error('[CsrfManager] Failed to refresh token', error);
|
|
throw error;
|
|
} finally {
|
|
this.isRefreshing = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update all tokens on the page
|
|
*/
|
|
updateAllTokens() {
|
|
if (!this.currentToken) {
|
|
return;
|
|
}
|
|
|
|
// Update meta tag
|
|
const metaTag = document.querySelector('meta[name="csrf-token"]');
|
|
if (metaTag) {
|
|
metaTag.setAttribute('content', this.currentToken);
|
|
}
|
|
|
|
// Update all form inputs
|
|
const formInputs = document.querySelectorAll(`input[name="${this.config.tokenName}"]`);
|
|
formInputs.forEach(input => {
|
|
input.value = this.currentToken;
|
|
});
|
|
|
|
// Update LiveComponent tokens
|
|
const liveComponents = document.querySelectorAll('[data-live-component]');
|
|
liveComponents.forEach(element => {
|
|
const tokenInput = element.querySelector(`input[name="${this.config.tokenName}"]`);
|
|
if (tokenInput) {
|
|
tokenInput.value = this.currentToken;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Start auto-refresh timer
|
|
*/
|
|
startAutoRefresh() {
|
|
if (this.refreshTimer) {
|
|
return;
|
|
}
|
|
|
|
this.refreshTimer = setInterval(() => {
|
|
this.refreshToken().catch(error => {
|
|
Logger.error('[CsrfManager] Auto-refresh failed', error);
|
|
});
|
|
}, this.config.refreshInterval);
|
|
|
|
Logger.debug('[CsrfManager] Auto-refresh started', {
|
|
interval: this.config.refreshInterval
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Stop auto-refresh timer
|
|
*/
|
|
stopAutoRefresh() {
|
|
if (this.refreshTimer) {
|
|
clearInterval(this.refreshTimer);
|
|
this.refreshTimer = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get token for use in fetch requests
|
|
*/
|
|
getTokenHeader() {
|
|
return {
|
|
[this.config.headerName]: this.currentToken
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Trigger token refreshed event
|
|
*/
|
|
triggerTokenRefreshedEvent(token) {
|
|
const event = new CustomEvent('csrf:token-refreshed', {
|
|
detail: { token },
|
|
bubbles: true
|
|
});
|
|
window.dispatchEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Destroy CSRF manager
|
|
*/
|
|
destroy() {
|
|
this.stopAutoRefresh();
|
|
this.currentToken = null;
|
|
Logger.info('[CsrfManager] Destroyed');
|
|
}
|
|
}
|
|
|