Files
Charon/frontend/src/api/settings.ts
GitHub Actions 0c90ab04d8 fix: login page warnings and implement secure URL testing
Fix browser console warnings on login page:
- Make COOP header conditional on development mode (suppress HTTP warnings)
- Add autocomplete attributes to 11 email/password inputs across 5 pages

Implement server-side URL testing with enterprise-grade SSRF protection:
- Replace window.open() with API-based connectivity check
- Block private IPs (RFC 1918, loopback, link-local, ULA, IPv6 ranges)
- DNS validation with 3s timeout before HTTP request
- Block AWS metadata endpoint (169.254.169.254)
- Block GCP metadata endpoint (metadata.google.internal)
- HTTP HEAD request with 5s timeout
- Maximum 2 redirects
- Admin-only access enforcement

Technical Implementation:
- Backend: url_testing.go utility with isPrivateIP validation
- Handler: TestPublicURL in settings_handler.go
- Route: POST /settings/test-url (authenticated, admin-only)
- Frontend: testPublicURL API call in settings.ts
- UI: testPublicURLHandler in SystemSettings.tsx with toast feedback

Test Coverage:
- Backend: 85.8% (72 SSRF protection test cases passing)
- Frontend: 86.85% (1,140 tests passing)
- Security scans: Clean (Trivy, Go vuln check)
- TypeScript: 0 type errors

Closes: [issue number if applicable]
2025-12-22 01:31:57 +00:00

58 lines
1.6 KiB
TypeScript

import client from './client'
/** Map of setting keys to string values. */
export interface SettingsMap {
[key: string]: string
}
/**
* Fetches all application settings.
* @returns Promise resolving to SettingsMap
* @throws {AxiosError} If the request fails
*/
export const getSettings = async (): Promise<SettingsMap> => {
const response = await client.get('/settings')
return response.data
}
/**
* Updates a single application setting.
* @param key - The setting key to update
* @param value - The new value for the setting
* @param category - Optional category for organization
* @param type - Optional type hint for the setting
* @throws {AxiosError} If the update fails
*/
export const updateSetting = async (key: string, value: string, category?: string, type?: string): Promise<void> => {
await client.post('/settings', { key, value, category, type })
}
/**
* Validates a URL for use as the application URL.
* @param url - The URL to validate
* @returns Promise resolving to validation result
*/
export const validatePublicURL = async (url: string): Promise<{
valid: boolean
normalized?: string
error?: string
}> => {
const response = await client.post('/settings/validate-url', { url })
return response.data
}
/**
* Tests if a URL is reachable from the server with SSRF protection.
* @param url - The URL to test
* @returns Promise resolving to test result with reachability status and latency
*/
export const testPublicURL = async (url: string): Promise<{
reachable: boolean
latency?: number
message?: string
error?: string
}> => {
const response = await client.post('/settings/test-url', { url })
return response.data
}