Phase 3: OpenSubsonic core (response envelope, error codes 42-44, getOpenSubsonicExtensions) and apiKeyAuthentication extension with tokenInfo, web API key management

This commit is contained in:
2026-08-23 08:04:01 -05:00
parent 3b4883780b
commit e38d31d384
17 changed files with 763 additions and 37 deletions
+30
View File
@@ -0,0 +1,30 @@
{{define "admin_api_keys"}}{{template "header" .}}
<p class="text-secondary small">All API keys across every user. Used for OpenSubsonic <code>apiKey=</code> authentication.</p>
{{if .Data}}
<table class="table vs-table table-sm">
<thead><tr><th>User</th><th>Name</th><th>Client</th><th>Created</th><th>Last used</th><th>Status</th><th></th></tr></thead>
<tbody>
{{range .Data}}
<tr>
<td>{{.Username}}</td>
<td>{{.Key.Name}}</td>
<td class="text-secondary small">{{.Key.ClientDescription}}</td>
<td class="text-secondary small">{{fmtTime .Key.CreatedAt}}</td>
<td class="text-secondary small">{{if .Key.LastUsedAt.Valid}}{{fmtTime .Key.LastUsedAt.Time}}{{else}}never{{end}}</td>
<td>{{if .Key.RevokedAt.Valid}}<span class="text-danger">Revoked</span>{{else}}<span class="text-success">Active</span>{{end}}</td>
<td class="text-end">
{{if not .Key.RevokedAt.Valid}}
<form method="post" action="/admin/api-keys/{{.Key.ID}}/revoke">
{{template "csrf_field" $.CSRFToken}}
<button type="submit" class="btn btn-sm btn-outline-danger">Revoke</button>
</form>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<p class="text-secondary">No API keys have been created yet.</p>
{{end}}
{{template "footer" .}}{{end}}
+1
View File
@@ -82,6 +82,7 @@
<li class="nav-item"><a class="nav-link {{if eq .Active "admin_settings"}}active{{end}}" href="/admin/settings"><i class="bi bi-sliders"></i> System Settings</a></li>
<li class="nav-item"><a class="nav-link {{if eq .Active "admin_podcasts"}}active{{end}}" href="/admin/podcasts"><i class="bi bi-broadcast"></i> Podcasts</a></li>
<li class="nav-item"><a class="nav-link {{if eq .Active "admin_jukebox"}}active{{end}}" href="/admin/jukebox"><i class="bi bi-cpu"></i> Jukebox</a></li>
<li class="nav-item"><a class="nav-link {{if eq .Active "admin_api_keys"}}active{{end}}" href="/admin/api-keys"><i class="bi bi-key"></i> API Keys</a></li>
{{end}}
</ul>
<div class="mt-auto px-3 py-2 text-secondary small">
+13 -7
View File
@@ -21,12 +21,13 @@
<div class="card mt-3">
<div class="card-body">
<h2 class="h6">API keys</h2>
<p class="text-secondary small">Used by third-party Subsonic clients in later phases.</p>
<form method="post" action="/settings/api-keys" class="d-flex gap-2 mb-3">
<h2 class="h6" id="api-keys">API keys</h2>
<p class="text-secondary small">Used by OpenSubsonic clients via <code>apiKey=</code> authentication, in place of your password.</p>
<form method="post" action="/settings/api-keys" class="row g-2 mb-3">
{{template "csrf_field" .CSRFToken}}
<input type="text" name="name" class="form-control form-control-sm" placeholder="Key name">
<button type="submit" class="btn btn-outline-primary btn-sm text-nowrap">New key</button>
<div class="col-5"><input type="text" name="name" class="form-control form-control-sm" placeholder="Key name" required></div>
<div class="col-5"><input type="text" name="client_description" class="form-control form-control-sm" placeholder="Client (optional)"></div>
<div class="col-2"><button type="submit" class="btn btn-outline-primary btn-sm w-100">New key</button></div>
</form>
{{if .Data.NewAPIKey}}
<div class="alert alert-warning small">
@@ -38,13 +39,18 @@
<tbody>
{{range .Data.APIKeys}}
<tr>
<td>{{.Name}}</td>
<td class="text-secondary small">{{fmtTime .CreatedAt}}</td>
<td>{{.Name}}{{if .ClientDescription}} <span class="text-secondary small">({{.ClientDescription}})</span>{{end}}</td>
<td class="text-secondary small">
{{fmtTime .CreatedAt}}
{{if .LastUsedAt.Valid}}· last used {{fmtTime .LastUsedAt.Time}}{{else}}· never used{{end}}
</td>
<td class="text-end">
{{if .RevokedAt.Valid}}<span class="text-danger small">Revoked</span>{{else}}
<form method="post" action="/settings/api-keys/{{.ID}}/revoke">
{{template "csrf_field" $.CSRFToken}}
<button type="submit" class="btn btn-sm btn-outline-danger">Revoke</button>
</form>
{{end}}
</td>
</tr>
{{end}}