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:
@@ -272,18 +272,39 @@ func buildLauncherApps(stackList []stacks.Stack, subdomains map[string]string) [
|
||||
return apps
|
||||
}
|
||||
|
||||
// launcherHandler renders the Indítópult: a grid of large tappable tiles, one per openable deployed
|
||||
// app (subdomain presence is the single openability criterion — see buildLauncherApps). Behind
|
||||
// RequireAuth like every page; the "/" landing page stays the Vezérlőpult.
|
||||
func (s *Server) launcherHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// launcherApps assembles the sorted launcher tile list (deployed/protected stacks with a subdomain,
|
||||
// controller excluded). Extracted from launcherHandler so the guest share page (v0.165.0) renders
|
||||
// the EXACT same app slice as the admin launcher.
|
||||
func (s *Server) launcherApps() []LauncherApp {
|
||||
var eligible []stacks.Stack
|
||||
for _, st := range s.stackMgr.GetStacks() {
|
||||
if st.Deployed || st.Protected {
|
||||
eligible = append(eligible, st)
|
||||
}
|
||||
}
|
||||
return buildLauncherApps(eligible, s.subdomainMap(eligible))
|
||||
}
|
||||
|
||||
// launcherHandler renders the Indítópult: a grid of large tappable tiles, one per openable deployed
|
||||
// app (subdomain presence is the single openability criterion — see buildLauncherApps). Behind
|
||||
// RequireAuth like every page; the "/" landing page stays the Vezérlőpult. It also carries the
|
||||
// "Indítópult megosztása" share state (v0.165.0) for the modal.
|
||||
func (s *Server) launcherHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := s.baseData("launcher", "Indítópult")
|
||||
data["Apps"] = buildLauncherApps(eligible, s.subdomainMap(eligible))
|
||||
data["Apps"] = s.launcherApps()
|
||||
|
||||
// Share modal state. The share URL is built from the request Host at render time (the canonical
|
||||
// controller subdomain is not persisted anywhere reachable here); it carries the live token, which
|
||||
// is fine to show the authed admin inside the modal — the ONE admin surface allowed to reveal it.
|
||||
token := s.settings.GetLauncherShareToken()
|
||||
data["ShareEnabled"] = token != ""
|
||||
if token != "" {
|
||||
data["ShareURL"] = "https://" + r.Host + "/s/" + token
|
||||
}
|
||||
data["SharePasswordSet"] = s.settings.GetLauncherSharePasswordHash() != ""
|
||||
if f := strings.TrimSpace(r.URL.Query().Get("flash")); f != "" {
|
||||
data["ShareFlash"] = f
|
||||
}
|
||||
s.executeTemplate(w, r, "launcher", data)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user