fix: CSRF regression — grace period for old sessions, GET check-port/refresh-ip, Peers.jsx native fetch tokens
- check_csrf() now issues a token for sessions that predate CSRF (existing logins) instead of blocking them - /api/wireguard/check-port and /api/wireguard/refresh-ip accept GET so native fetch calls bypass the token requirement - WireGuard.jsx: changed three native fetch POST → GET for the above endpoints - Peers.jsx: add X-CSRF-Token header to three native fetch mutation calls (calendar collection, peer PUT, clear-reinstall) - api.js: export getCsrfToken() so non-Axios callers can read the current token Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+9
-3
@@ -261,8 +261,14 @@ def check_csrf():
|
||||
path = request.path
|
||||
if not path.startswith('/api/') or path.startswith('/api/auth/'):
|
||||
return None
|
||||
token_header = request.headers.get('X-CSRF-Token')
|
||||
token_session = session.get('csrf_token')
|
||||
if not token_session:
|
||||
# Session predates CSRF tokens (existing login) — issue a token now so
|
||||
# the next request can carry it. Don't block this request; the client
|
||||
# couldn't have known the token yet.
|
||||
session['csrf_token'] = secrets.token_hex(32)
|
||||
return None
|
||||
token_header = request.headers.get('X-CSRF-Token')
|
||||
if not token_header or token_header != token_session:
|
||||
return jsonify({'error': 'CSRF token missing or invalid'}), 403
|
||||
return None
|
||||
@@ -1762,7 +1768,7 @@ def get_server_config():
|
||||
logger.error(f"Error getting server config: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/wireguard/refresh-ip', methods=['POST'])
|
||||
@app.route('/api/wireguard/refresh-ip', methods=['GET', 'POST'])
|
||||
def refresh_external_ip():
|
||||
try:
|
||||
ip = wireguard_manager.get_external_ip(force_refresh=True)
|
||||
@@ -1788,7 +1794,7 @@ def apply_wireguard_enforcement():
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/wireguard/check-port', methods=['POST'])
|
||||
@app.route('/api/wireguard/check-port', methods=['GET', 'POST'])
|
||||
def check_wireguard_port():
|
||||
try:
|
||||
port_open = wireguard_manager.check_port_open()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Plus, Trash2, Edit, Eye, Shield, Copy, Download, Key, AlertTriangle, CheckCircle, Globe, Lock, Users, Server } from 'lucide-react';
|
||||
import { peerRegistryAPI, wireguardAPI } from '../services/api';
|
||||
import { peerRegistryAPI, wireguardAPI, getCsrfToken } from '../services/api';
|
||||
import { useConfig } from '../contexts/ConfigContext';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
@@ -194,7 +194,11 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
||||
|
||||
if (formData.create_calendar) {
|
||||
try {
|
||||
await fetch(`/api/calendar/create-user-collection?user=${formData.name}`, { method: 'POST', credentials: 'include' });
|
||||
await fetch(`/api/calendar/create-user-collection?user=${formData.name}`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'X-CSRF-Token': getCsrfToken() || '' },
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -225,7 +229,7 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
||||
const r = await fetch(`/api/peers/${selectedPeer.name}`, {
|
||||
method: 'PUT',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrfToken() || '' },
|
||||
body: JSON.stringify({
|
||||
description: formData.description,
|
||||
internet_access: formData.internet_access,
|
||||
@@ -292,7 +296,11 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
||||
|
||||
const handleConfigDownloaded = async (peerName) => {
|
||||
try {
|
||||
await fetch(`/api/peers/${peerName}/clear-reinstall`, { method: 'POST', credentials: 'include' });
|
||||
await fetch(`/api/peers/${peerName}/clear-reinstall`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'X-CSRF-Token': getCsrfToken() || '' },
|
||||
});
|
||||
setPeers(ps => ps.map(p => p.name === peerName ? { ...p, config_needs_reinstall: false } : p));
|
||||
} catch {}
|
||||
};
|
||||
|
||||
@@ -29,11 +29,11 @@ function WireGuard() {
|
||||
setIsRefreshingIp(true);
|
||||
try {
|
||||
// Refresh IP first (fast)
|
||||
const ipResp = await fetch('/api/wireguard/refresh-ip', { method: 'POST', credentials: 'include' });
|
||||
const ipResp = await fetch('/api/wireguard/refresh-ip', { credentials: 'include' });
|
||||
const ipData = await ipResp.json();
|
||||
setServerConfig(prev => ({ ...prev, ...ipData, port_open: 'checking' }));
|
||||
// Then check port (slow — external call)
|
||||
const portResp = await fetch('/api/wireguard/check-port', { method: 'POST', credentials: 'include' });
|
||||
const portResp = await fetch('/api/wireguard/check-port', { credentials: 'include' });
|
||||
const portData = await portResp.json();
|
||||
setServerConfig(prev => ({ ...prev, port_open: portData.port_open }));
|
||||
} catch (e) {
|
||||
@@ -56,7 +56,7 @@ function WireGuard() {
|
||||
if (serverConfigResponse) {
|
||||
setServerConfig({ ...serverConfigResponse, port_open: 'checking' });
|
||||
// Check port asynchronously so page loads fast
|
||||
fetch('/api/wireguard/check-port', { method: 'POST', credentials: 'include' })
|
||||
fetch('/api/wireguard/check-port', { credentials: 'include' })
|
||||
.then(r => r.json())
|
||||
.then(d => setServerConfig(prev => ({ ...prev, port_open: d.port_open ?? false })))
|
||||
.catch(() => setServerConfig(prev => ({ ...prev, port_open: false })));
|
||||
|
||||
@@ -11,6 +11,10 @@ export function setCsrfToken(token) {
|
||||
_csrfToken = token;
|
||||
}
|
||||
|
||||
export function getCsrfToken() {
|
||||
return _csrfToken;
|
||||
}
|
||||
|
||||
// Create axios instance with base configuration
|
||||
const api = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL || '',
|
||||
|
||||
Reference in New Issue
Block a user