refactor(confighttp): HTML page handlers into generic getPage function (#4645)

This commit is contained in:
David Lane
2026-03-08 18:36:22 -04:00
committed by GitHub
parent 3fbbe88b79
commit f04f6a2bde
13 changed files with 1250 additions and 275 deletions

View File

@@ -1,15 +1,21 @@
function generateExamples(endpoint, method, body = null) {
let curlBodyString = '';
let curlHeaderString = '';
let psBodyString = '';
let psContentTypeString = '';
let psBodyParams = '';
if (body) {
const curlJsonString = JSON.stringify(body).replace(/"/g, '\\"');
curlBodyString = ` -d "${curlJsonString}"`;
curlHeaderString = ' -H "Content-Type: application/json"';
psBodyString = `-Body (ConvertTo-Json ${JSON.stringify(body)})`;
psContentTypeString = '-ContentType \'application/json\'';
psBodyParams = ' `\n ' + psBodyString + ' `\n ' + psContentTypeString;
}
return {
cURL: `curl -u user:pass -H "Content-Type: application/json" -X ${method.trim()} -k https://localhost:47990${endpoint.trim()}${curlBodyString}`,
cURL: `curl -u user:pass${curlHeaderString} -X ${method.trim()} -k https://localhost:47990${endpoint.trim()}${curlBodyString}`,
Python: `import json
import requests
from requests.auth import HTTPBasicAuth
@@ -22,19 +28,18 @@ requests.${method.trim().toLowerCase()}(
JavaScript: `fetch('https://localhost:47990${endpoint.trim()}', {
method: '${method.trim()}',
headers: {
'Authorization': 'Basic ' + btoa('user:pass'),
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa('user:pass'),${body ? `\n 'Content-Type': 'application/json',` : ''}
}${body ? `,\n body: JSON.stringify(${JSON.stringify(body)}),` : ''}
})
.then(response => response.json())
.then(data => console.log(data));`,
PowerShell: `Invoke-RestMethod \`
-SkipCertificateCheck \`
-ContentType 'application/json' \`
-Uri 'https://localhost:47990${endpoint.trim()}' \`
-Method ${method.trim()} \`
-Headers @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes('user:pass'))}
${psBodyString}`
-Headers @{
Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes('user:pass'))
}${psBodyParams}`
};
}

View File

@@ -5,10 +5,38 @@ Sunshine has a RESTful API which can be used to interact with the service.
Unless otherwise specified, authentication is required for all API calls. You can authenticate using
basic authentication with the admin username and password.
## CSRF Protection
State-changing API endpoints (POST, DELETE) are protected against Cross-Site Request Forgery (CSRF) attacks.
**For Web Browsers:**
- Requests from same-origin (configured via `csrf_allowed_origins`) are automatically allowed
- Cross-origin requests require a CSRF token
**For Non-Browser Applications:**
- Non-browser clients (e.g. `curl`, scripts, custom apps) are **exempt** from CSRF protection
- CSRF attacks require a browser to silently attach credentials to a cross-origin request — this threat
does not apply to non-browser clients that explicitly provide credentials with every request
- Requests with no `Origin` or `Referer` header (as is typical for non-browser clients) are automatically
allowed without a CSRF token
**Example (browser-equivalent cross-origin request):**
```bash
# Get CSRF token
curl -u user:pass https://localhost:47990/api/csrf-token
# Use token in request
curl -u user:pass -H "X-CSRF-Token: your_token_here" \
-X POST https://localhost:47990/api/restart
```
@htmlonly
<script src="api.js"></script>
@endhtmlonly
## GET /api/csrf-token
@copydoc confighttp::getCSRFToken()
## GET /api/apps
@copydoc confighttp::getApps()

View File

@@ -1606,6 +1606,35 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>
### csrf_allowed_origins
<table>
<tr>
<td>Description</td>
<td colspan="2">
Comma-separated list of additional allowed origins for CSRF protection. These origins will be
appended to the default allowed origins (localhost variants and the configured web UI port).
Requests from allowed origins can access state-changing API endpoints without CSRF tokens.
<br><br>
@attention{Only add origins you trust. Each origin must be a complete URL prefix
including protocol and host (e.g., https://example.com). Port numbers are optional.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
(empty - uses built-in defaults: https://localhost, https://127.0.0.1, https://[::1],
with configured UI port variants)
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
csrf_allowed_origins = https://myapp.local,https://custom.domain.com
@endcode</td>
</tr>
</table>
### external_ip
<table>