fix: Gitea Traefik routing and connection pool optimization
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
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
This commit is contained in:
221
resources/js/modules/security/CsrfManager.js
Normal file
221
resources/js/modules/security/CsrfManager.js
Normal file
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
}
|
||||
|
||||
254
resources/js/modules/security/SecurityManager.js
Normal file
254
resources/js/modules/security/SecurityManager.js
Normal file
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* Security Manager
|
||||
*
|
||||
* Provides security-related utilities including CSRF, XSS protection, and CSP helpers.
|
||||
*/
|
||||
|
||||
import { Logger } from '../../core/logger.js';
|
||||
import { CsrfManager } from './CsrfManager.js';
|
||||
|
||||
/**
|
||||
* SecurityManager - Centralized security utilities
|
||||
*/
|
||||
export class SecurityManager {
|
||||
constructor(config = {}) {
|
||||
this.config = {
|
||||
csrf: config.csrf || {},
|
||||
xss: {
|
||||
enabled: config.xss?.enabled ?? true,
|
||||
sanitizeOnInput: config.xss?.sanitizeOnInput ?? false
|
||||
},
|
||||
csp: {
|
||||
enabled: config.csp?.enabled ?? false,
|
||||
reportOnly: config.csp?.reportOnly ?? false
|
||||
},
|
||||
...config
|
||||
};
|
||||
|
||||
this.csrfManager = null;
|
||||
|
||||
// Initialize
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new SecurityManager instance
|
||||
*/
|
||||
static create(config = {}) {
|
||||
return new SecurityManager(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize security manager
|
||||
*/
|
||||
init() {
|
||||
// Initialize CSRF manager
|
||||
this.csrfManager = CsrfManager.create(this.config.csrf);
|
||||
|
||||
// Initialize XSS protection if enabled
|
||||
if (this.config.xss.enabled) {
|
||||
this.initXssProtection();
|
||||
}
|
||||
|
||||
// Initialize CSP if enabled
|
||||
if (this.config.csp.enabled) {
|
||||
this.initCsp();
|
||||
}
|
||||
|
||||
Logger.info('[SecurityManager] Initialized', {
|
||||
csrf: !!this.csrfManager,
|
||||
xss: this.config.xss.enabled,
|
||||
csp: this.config.csp.enabled
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize XSS protection
|
||||
*/
|
||||
initXssProtection() {
|
||||
// Add input sanitization if enabled
|
||||
if (this.config.xss.sanitizeOnInput) {
|
||||
document.addEventListener('input', (event) => {
|
||||
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
|
||||
this.sanitizeInput(event.target);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize input value
|
||||
*/
|
||||
sanitizeInput(element) {
|
||||
const originalValue = element.value;
|
||||
const sanitized = this.sanitizeHtml(originalValue);
|
||||
|
||||
if (sanitized !== originalValue) {
|
||||
element.value = sanitized;
|
||||
Logger.warn('[SecurityManager] Sanitized potentially dangerous input', {
|
||||
field: element.name || element.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize HTML string
|
||||
*/
|
||||
sanitizeHtml(html) {
|
||||
if (typeof html !== 'string') {
|
||||
return html;
|
||||
}
|
||||
|
||||
// Remove script tags and event handlers
|
||||
return html
|
||||
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
||||
.replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')
|
||||
.replace(/javascript:/gi, '')
|
||||
.replace(/data:text\/html/gi, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Content Security Policy
|
||||
*/
|
||||
initCsp() {
|
||||
// CSP is typically set server-side, but we can validate it client-side
|
||||
const cspHeader = this.getCspHeader();
|
||||
|
||||
if (cspHeader) {
|
||||
Logger.debug('[SecurityManager] CSP header found', cspHeader);
|
||||
this.validateCsp(cspHeader);
|
||||
} else {
|
||||
Logger.warn('[SecurityManager] No CSP header found');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSP header from meta tag or response headers
|
||||
*/
|
||||
getCspHeader() {
|
||||
// Try meta tag
|
||||
const metaTag = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
|
||||
if (metaTag) {
|
||||
return metaTag.getAttribute('content');
|
||||
}
|
||||
|
||||
// Note: Response headers are not accessible from JavaScript
|
||||
// This would need to be passed from server or checked server-side
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate CSP header
|
||||
*/
|
||||
validateCsp(cspHeader) {
|
||||
// Basic validation - check for common security directives
|
||||
const requiredDirectives = ['default-src', 'script-src', 'style-src'];
|
||||
const directives = cspHeader.split(';').map(d => d.trim().split(' ')[0]);
|
||||
|
||||
const missing = requiredDirectives.filter(dir =>
|
||||
!directives.some(d => d.toLowerCase() === dir.toLowerCase())
|
||||
);
|
||||
|
||||
if (missing.length > 0) {
|
||||
Logger.warn('[SecurityManager] CSP missing recommended directives', missing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSRF token
|
||||
*/
|
||||
getCsrfToken() {
|
||||
return this.csrfManager?.getToken() || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSRF token header
|
||||
*/
|
||||
getCsrfTokenHeader() {
|
||||
return this.csrfManager?.getTokenHeader() || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh CSRF token
|
||||
*/
|
||||
async refreshCsrfToken() {
|
||||
if (this.csrfManager) {
|
||||
return await this.csrfManager.refreshToken();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate security headers
|
||||
*/
|
||||
validateSecurityHeaders() {
|
||||
const issues = [];
|
||||
|
||||
// Check for HTTPS
|
||||
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
|
||||
issues.push('Not using HTTPS');
|
||||
}
|
||||
|
||||
// Check for CSP
|
||||
if (!this.getCspHeader()) {
|
||||
issues.push('No Content Security Policy header');
|
||||
}
|
||||
|
||||
// Check for X-Frame-Options
|
||||
// Note: Headers are not accessible from JavaScript, would need server-side check
|
||||
|
||||
return {
|
||||
valid: issues.length === 0,
|
||||
issues
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML to prevent XSS
|
||||
*/
|
||||
escapeHtml(text) {
|
||||
if (typeof text !== 'string') {
|
||||
return text;
|
||||
}
|
||||
|
||||
const map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
return text.replace(/[&<>"']/g, m => map[m]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate URL to prevent XSS
|
||||
*/
|
||||
validateUrl(url) {
|
||||
try {
|
||||
const parsed = new URL(url, window.location.origin);
|
||||
|
||||
// Block javascript: and data: URLs
|
||||
if (parsed.protocol === 'javascript:' || parsed.protocol === 'data:') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy security manager
|
||||
*/
|
||||
destroy() {
|
||||
if (this.csrfManager) {
|
||||
this.csrfManager.destroy();
|
||||
this.csrfManager = null;
|
||||
}
|
||||
|
||||
Logger.info('[SecurityManager] Destroyed');
|
||||
}
|
||||
}
|
||||
|
||||
86
resources/js/modules/security/index.js
Normal file
86
resources/js/modules/security/index.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Security Module
|
||||
*
|
||||
* Provides security-related utilities including CSRF, XSS protection, and CSP helpers.
|
||||
*
|
||||
* Usage:
|
||||
* - Add data-module="security" to enable global security features
|
||||
* - Or import and use directly: import { SecurityManager } from './modules/security/index.js'
|
||||
*
|
||||
* Features:
|
||||
* - CSRF token management and auto-refresh
|
||||
* - XSS protection helpers
|
||||
* - Content Security Policy helpers
|
||||
* - Security headers validation
|
||||
*/
|
||||
|
||||
import { Logger } from '../../core/logger.js';
|
||||
import { SecurityManager } from './SecurityManager.js';
|
||||
import { CsrfManager } from './CsrfManager.js';
|
||||
|
||||
const SecurityModule = {
|
||||
name: 'security',
|
||||
securityManager: null,
|
||||
|
||||
init(config = {}, state = null) {
|
||||
Logger.info('[SecurityModule] Module initialized');
|
||||
|
||||
// Create security manager
|
||||
this.securityManager = SecurityManager.create(config);
|
||||
|
||||
// Expose globally for easy access
|
||||
if (typeof window !== 'undefined') {
|
||||
window.SecurityManager = this.securityManager;
|
||||
window.CsrfManager = this.securityManager.csrfManager;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get security manager instance
|
||||
*/
|
||||
getSecurityManager() {
|
||||
return this.securityManager || SecurityManager.create();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get CSRF token
|
||||
*/
|
||||
getCsrfToken() {
|
||||
return this.securityManager?.getCsrfToken() || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh CSRF token
|
||||
*/
|
||||
async refreshCsrfToken() {
|
||||
if (this.securityManager) {
|
||||
return await this.securityManager.refreshCsrfToken();
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (this.securityManager) {
|
||||
this.securityManager.destroy();
|
||||
this.securityManager = null;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
delete window.SecurityManager;
|
||||
delete window.CsrfManager;
|
||||
}
|
||||
|
||||
Logger.info('[SecurityModule] Module destroyed');
|
||||
}
|
||||
};
|
||||
|
||||
// Export for direct usage
|
||||
export { SecurityManager, CsrfManager };
|
||||
|
||||
// Export as default for module system
|
||||
export default SecurityModule;
|
||||
|
||||
// Export init function for module system
|
||||
export const init = SecurityModule.init.bind(SecurityModule);
|
||||
|
||||
Reference in New Issue
Block a user