feat(operator): gate op.console to staff and land owner setup there
The operator console (op.console.<root>) requires internal permission verification on top of Zero-Trust: a passkey is not access. requireExternal now refuses any non-admin principal arriving on the admin host, before any handler, so op.console is staff-only at the door rather than per-route — including on the passwordless demo face where Cloudflare Access is not in front. The gate is inert on the player console (console.<root>). Owner first-run setup is staff onboarding, so `felis setup` mints the one-time setup URL on op.console.<root>/setup (was console.<root>). The passkey verifier lists both console and op.console in RPOrigins so the one-time binding asserts on either face under the shared console.<root> RP-ID. Session admin-access now includes role=owner, not only admin: the owner is a superset of admin, so excluding it left IsOwner() unreachable through a passwordless session. No path assigns role=owner yet — this is forward consistency. The bootstrap summary now names console.<root> the player panel and op.console.<root> the operator console where the Owner runs setup, fixing text that told operators not to run setup there. Tests: op.console door gate (non-admin refused, player console unaffected, admin passes) and owner session admin-access; the setup-bind default-host test follows the move to op.console.
This commit is contained in:
11 files changed
+127
-35
No files matched your search
@@ -388,17 +388,18 @@ func newSetupToken() (raw, hash string, err error) {
|
||||
// binds their Minecraft account via a one-time link code the login gate handed
|
||||
// them in-game, the bound user is promoted to role='admin' (passwordless Owner),
|
||||
// local auth is enabled, and a one-time setup URL is minted for the first web
|
||||
// login where the Owner verifies email / enrolls a passkey. panelHostname is the
|
||||
// panel host the URL points at: the wizard enrolls the passkey, and the only wired
|
||||
// WebAuthn verifier (cmd/felis/api.go) is scoped to the panel host, so the
|
||||
// ceremony's origin MUST be the panel face — op.console has no verifier wired and
|
||||
// cannot enroll at all. osUser is recorded as the accountable actor.
|
||||
// login where the Owner verifies email / enrolls a passkey. adminHostname is the
|
||||
// operator-console host the URL points at (op.console.<root>): the Owner is staff,
|
||||
// so first-run onboarding belongs on the operator face, not the player panel. The
|
||||
// passkey verifier's RP id is the panel host, but its permitted origins now include
|
||||
// op.console (cmd/felis/api.go), so enrollment on op.console is a valid ceremony —
|
||||
// one binding that works on both faces. osUser is recorded as the accountable actor.
|
||||
//
|
||||
// Local auth is as load-bearing here as it is in break-glass, and for a sharper
|
||||
// reason: an MC-bound Owner has no password AND no email, so the setup token is
|
||||
// their ONLY door. CompleteOwnerSetup therefore commits the identity bind, auth
|
||||
// toggle, and token together; any failed write leaves the link code retryable.
|
||||
func performSetupMCBind(ctx context.Context, s ownerStore, code, panelHostname, osUser string) (breakGlassOutcome, error) {
|
||||
func performSetupMCBind(ctx context.Context, s ownerStore, code, adminHostname, osUser string) (breakGlassOutcome, error) {
|
||||
code = strings.TrimSpace(strings.ToUpper(code))
|
||||
if code == "" {
|
||||
return breakGlassOutcome{}, errors.New("link code is required")
|
||||
@@ -423,9 +424,9 @@ func performSetupMCBind(ctx context.Context, s ownerStore, code, panelHostname,
|
||||
ownerIdentity: mcUUID,
|
||||
auditErr: auditSetupMCBind(ctx, s, osUser, mcUUID, authSource),
|
||||
}
|
||||
host := strings.TrimSpace(panelHostname)
|
||||
host := strings.TrimSpace(adminHostname)
|
||||
if host == "" {
|
||||
host = "console.localhost"
|
||||
host = "op.console.localhost"
|
||||
}
|
||||
out.setupTokenURL = "https://" + host + "/setup?token=" + raw
|
||||
return out, nil
|
||||
|
||||
Reference in new issue
Block a user