feat(auth): add Owner authentication source settings

Manage Yggdrasil providers from the panel using durable platform settings, protected identity namespaces and atomic revisions. Apply changes to subsequent logins and profile lookups without restarting. Return operator-host logouts to the login method selection page.
This commit is contained in:
Lemon-miaow committed 2026-10-04 22:18:37 +08:00
1 parent 2c98e8b2ab
commit 61b283ae2f
33 files changed
+1466 -18

No files matched your search

+131
View File
@@ -311,6 +311,42 @@ components:
output: { type: string }
schemas:
AuthSourceConfig:
type: object
additionalProperties: false
required: [tag, prefix, url, api_url, enabled]
properties:
tag:
type: string
description: Permanent UUID namespace; saved tags cannot be renamed or removed. mojang is reserved.
prefix:
type: string
pattern: '^[A-Za-z0-9]{1,4}$'
description: Unique case-insensitive display prefix.
url:
type: string
description: hasJoined endpoint; HTTPS required except for localhost or private literal IP addresses. No query or fragment.
api_url:
type: string
description: Optional Yggdrasil API base for role lookup; empty infers it from the standard hasJoined suffix.
enabled: { type: boolean }
AuthSourcesSettings:
type: object
required: [sources, revision, managed]
properties:
sources:
type: array
maxItems: 32
description: Third-party sources in priority order; built-in Mojang always precedes them and is immutable.
items: { $ref: '#/components/schemas/AuthSourceConfig' }
revision:
type: string
description: Opaque revision to send unchanged when saving; stale or concurrent writes return 409.
managed:
type: boolean
description: True when stored in platform_settings; false while using installation TOML defaults.
Error:
type: object
description: Uniform error envelope emitted by every handler (internal/api/errors.go).
@@ -4229,6 +4265,101 @@ paths:
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/settings/auth-sources:
get:
tags: [account]
operationId: getAuthSources
summary: Read authentication sources (Owner).
x-felis-face: [external]
x-felis-tier: owner
security: [{ sessionCookie: [] }]
responses:
'200':
description: Current third-party sources and their revision.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthSourcesSettings' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'503': { $ref: '#/components/responses/ServiceUnavailable' }
put:
tags: [account]
operationId: setAuthSources
summary: Save authentication sources (Owner, fresh reauthentication).
description: >-
Atomically persists an override in platform_settings. It applies to the next
login and role lookup on every API replica without restarting; existing
players stay online. Tags identify permanent UUID namespaces; retain every
saved tag and disable unwanted sources. Mojang remains built-in and trusted,
while configured sources always remain third-party. Nano remains TOML-only.
x-felis-face: [external]
x-felis-tier: owner
security: [{ sessionCookie: [] }]
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [sources, revision]
properties:
sources:
type: array
maxItems: 32
items: { $ref: '#/components/schemas/AuthSourceConfig' }
revision: { type: string }
responses:
'200':
description: Saved configuration and new revision.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthSourcesSettings' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'409':
description: auth_sources_changed or auth_source_tag_locked; reload instead of overwriting another Owner's changes.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
'503': { $ref: '#/components/responses/ServiceUnavailable' }
/api/v1/settings/auth-sources/test:
post:
tags: [account]
operationId: testAuthSource
summary: Probe a hasJoined endpoint (Owner).
description: >-
Checks an unsaved source with a fresh random serverId. A healthy endpoint
returns 204 for a session that never joined. Uses a five-second timeout,
verified TLS and no redirects. This tests connectivity and hasJoined
behavior, not launcher login or profile lookup. Does not save configuration.
x-felis-face: [external]
x-felis-tier: owner
security: [{ sessionCookie: [] }]
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/AuthSourceConfig' }
responses:
'200':
description: Probe result; non-204 status has ok=false.
content:
application/json:
schema:
type: object
required: [ok, status, elapsed_ms]
properties:
ok: { type: boolean }
status: { type: integer }
elapsed_ms: { type: integer, format: int64 }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'503': { $ref: '#/components/responses/ServiceUnavailable' }
/api/v1/updates/window:
get:
tags: [admin-updates]
+29
View File
@@ -874,3 +874,32 @@ the moved domain and left `check` clean; moving back restored every surface
on every run, so a host upgraded from one can show that line once with the file already
on the names; `sudo systemctl restart felis-velocity` clears it. The installer now leaves
the file alone when its content is the same.
## 7. Authentication sources
On the operator console, the Owner's **Platform → Authentication sources** page
(`/admin/auth-sources`) manages third-party Yggdrasil providers. It imports the
installation's `[[auth_source]]` list on first use. Save stores the ordered list
in `platform_settings.auth_sources`; that override then takes precedence over TOML
and is read by every full-API replica for the next game login and role lookup.
No restart is required, and existing players stay connected. Nano continues to
use its TOML list. A database read failure refuses new authentication rather than
falling back to an obsolete or disabled provider.
Mojang remains enabled and first, retaining official UUIDs. Every third-party
provider uses a permanent tag as its UUID namespace; saved or imported tags cannot
be renamed or removed. Disable a provider to stop accepting its logins, or enable
it again to restore the same identities. Changing a provider's endpoint changes
who verifies identities in that namespace; keep it pointed at the same trusted
service. Prefixes are 1–4 letters/digits and must be unique regardless of case.
Set the full `hasJoined` URL. Standard paths infer the profile-query API root;
nonstandard paths need an explicit API root for role-name/UUID lookup. HTTPS is
required, except for localhost or literal private IPs; query strings and fragments
are rejected. Launchers must authenticate with the same provider. **Test connection**
probes an unused session and expects HTTP 204; it does not save, verify launcher
configuration, or test the profile-query API.
Saving requires Owner access on the operator host and recent reauthentication for
a local session. A revision conflict preserves the draft; discard it and reload
before editing the newer configuration.