v0.165.0: Indítópult megosztása — guest launcher via capability URL (+ optional password, QR)
Mint a 160-bit capability URL (/s/<token>) serving a standalone read-only guest launcher: same tiles, opens apps in new tabs, no account, no admin session. Information only, zero control — every privilege stays behind each app's own auth. - /s/ pre-auth pass-through (after the claim gate) + session-CSRF exemption; guest password POST carries its own pre-auth HMAC CSRF. - Constant-time token match; empty stored token = disabled = byte-identical mux 404. - Optional per-share password: separate bcrypt hash + own attempt map; signed cookie = HMAC(token|passwordHash) keyed with web.session_secret, so rotate/change invalidates. - Guest labels ride the v0.164.0 ruling; never expose internal state vocabulary. - Token redacted in logs (/s/<redacted>); never in CHANGELOG/REPORT/CONTEXT. - Admin modal: copy-link, QR (go-qrcode), set/clear password, rotate, disable. - Tests: Groups A-G (14) + 3 red-proofs verified red.
This commit is contained in:
@@ -28,6 +28,14 @@ type Settings struct {
|
||||
// Auth
|
||||
PasswordHash string `json:"password_hash,omitempty"` // bcrypt hash, overrides controller.yaml
|
||||
|
||||
// Guest launcher share (v0.165.0). LauncherShareToken is the ≥160-bit URL capability token that
|
||||
// serves the read-only guest launcher at /s/<token>; empty means sharing is OFF (there is no
|
||||
// separate enabled flag — an empty token matches nothing). LauncherSharePasswordHash is an
|
||||
// OPTIONAL bcrypt hash for a per-share password, ALWAYS SEPARATE from the admin PasswordHash above.
|
||||
// The token is a secret and must never be logged.
|
||||
LauncherShareToken string `json:"launcher_share_token,omitempty"`
|
||||
LauncherSharePasswordHash string `json:"launcher_share_password_hash,omitempty"`
|
||||
|
||||
// Customer-claim arc (v0.122.0, F-4). Claimed is SET-ONLY (a claim or reset completed at
|
||||
// least once — never cleared). ClaimCode* cache the freshest hub-delivered code state (report
|
||||
// ACK; beats controller.yaml when its generation is newer). ClaimConsumedGeneration records
|
||||
@@ -482,6 +490,42 @@ func (s *Settings) SetPasswordHash(hash string) error {
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// ── Guest launcher share (v0.165.0) ──────────────────────────────────────────────
|
||||
|
||||
// GetLauncherShareToken returns the guest-launcher capability token ("" = sharing disabled).
|
||||
func (s *Settings) GetLauncherShareToken() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.LauncherShareToken
|
||||
}
|
||||
|
||||
// SetLauncherShareToken stores (or clears, on "") the guest-launcher token and saves. A new value
|
||||
// rotates the link; because the guest gate cookie is bound to the token, any outstanding cookie is
|
||||
// invalidated automatically. Never log the value.
|
||||
func (s *Settings) SetLauncherShareToken(token string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.LauncherShareToken = token
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// GetLauncherSharePasswordHash returns the optional per-share bcrypt hash ("" = no share password).
|
||||
func (s *Settings) GetLauncherSharePasswordHash() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.LauncherSharePasswordHash
|
||||
}
|
||||
|
||||
// SetLauncherSharePasswordHash stores (or clears, on "") the per-share bcrypt hash and saves. It is
|
||||
// ALWAYS distinct from the admin password hash. Changing it invalidates outstanding guest cookies
|
||||
// (they bind the hash into the signature).
|
||||
func (s *Settings) SetLauncherSharePasswordHash(hash string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.LauncherSharePasswordHash = hash
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// ── Customer-claim arc (v0.122.0) ──────────────────────────────────────────────
|
||||
|
||||
// GetClaimed reports whether this box has completed a claim (set-only).
|
||||
|
||||
Reference in New Issue
Block a user