fix(auth): hand off privileges before state creation; fail closed on store errors

Fresh boots opened the owner/token/session stores before EnsureDataRoot
dropped to the host user, leaving a root-owned maestro/ state dir whose
EACCES surfaced as a bogus ERR_INVALID_CREDENTIALS on /api/auth/setup.
main() now runs the handoff first; EnsureDataRoot heals pre-existing
root-owned state dirs while still root (healOwnerState).

Storage failures from the owner store now answer 500 ERR_INTERNAL
everywhere (status/setup/login/change-password/RequireSession/
GateWebSocket) instead of blaming credentials or re-advertising setup
mode; SetupRequired returns the store error instead of swallowing it,
and FileOwnerStore.Verify propagates read errors rather than masking
them as unknown-user.
This commit is contained in:
2026-08-23 12:10:15 +01:00
parent 707fe619f7
commit c014b2abfb
7 changed files with 228 additions and 23 deletions
+9 -2
View File
@@ -2,7 +2,7 @@ openapi: 3.0.3
info:
title: Dcntrlized Maestro API
version: 1.0.0
description: The API of the Maestro orchestration router. Maestro manages the lifecycle of Dcntrlized service containers and routes inter-service action calls to them. Every /api/* route except /api/auth/* and the deliberately public GET /api/instance (hub-app theme) requires an instance-owner session (cookies issued by the auth endpoints); in setup mode gated routes answer 428 ERR_SETUP_REQUIRED, otherwise 401 ERR_UNAUTHENTICATED. Mutating requests from browser origins are additionally checked against the request host (CSRF).
description: The API of the Maestro orchestration router. Maestro manages the lifecycle of Dcntrlized service containers and routes inter-service action calls to them. Every /api/* route except /api/auth/* and the deliberately public GET /api/instance (hub-app theme) requires an instance-owner session (cookies issued by the auth endpoints); in setup mode gated routes answer 428 ERR_SETUP_REQUIRED, otherwise 401 ERR_UNAUTHENTICATED (an unreadable owner store fails closed with 500 ERR_INTERNAL). Mutating requests from browser origins are additionally checked against the request host (CSRF).
components:
schemas:
@@ -616,7 +616,8 @@ paths:
The SPA bootstraps from this endpoint. All other /api/* routes are
gated by the instance-owner session (onboarding theme M1): in setup
mode they answer 428 ERR_SETUP_REQUIRED, otherwise 401
ERR_UNAUTHENTICATED without a valid session.
ERR_UNAUTHENTICATED without a valid session; an unreadable owner store
fails closed with 500 ERR_INTERNAL everywhere.
responses:
'200':
description: Gate state
@@ -650,6 +651,8 @@ paths:
description: Setup already completed (ERR_SETUP_COMPLETED)
'429':
description: Rate limited (ERR_RATE_LIMITED)
'500':
description: Owner store unreadable/unwritable (ERR_INTERNAL)
/api/auth/login:
post:
@@ -674,6 +677,8 @@ paths:
description: Unknown user or wrong password (ERR_INVALID_CREDENTIALS)
'429':
description: Rate limited (ERR_RATE_LIMITED) with Retry-After
'500':
description: Owner store unreadable (ERR_INTERNAL)
/api/auth/refresh:
post:
@@ -721,6 +726,8 @@ paths:
description: Not authenticated
'429':
description: Rate limited (ERR_RATE_LIMITED)
'500':
description: Owner store unreadable/unwritable (ERR_INTERNAL)
/api/ws:
get: