v0.333.0: the file manager's own token ends with the dashboard sign-out (R-1155); the recovery-code page says the code becomes live after the check (R-1161)
gates / gates (push) Successful in 49s
gates / gates (push) Successful in 49s
This commit is contained in:
@@ -29,6 +29,9 @@ import (
|
||||
// pass for the files cookie (`felhom_files`, host-only on files.<domain>, HttpOnly, Secure, Lax) and goes on to the
|
||||
// folder. A valid files cookie → 200 WITH the box's secret header naming the household's FileBrowser user. No files
|
||||
// cookie → 200 WITHOUT it: FileBrowser asks for its own password (`admin` — the fallback, and the operator's way).
|
||||
// R-1155: with no valid files cookie, a request that carries a NON-admin FileBrowser token (the household user's,
|
||||
// kept by a browser that lost only the files cookie, or copied out of one) is refused like a stale files cookie —
|
||||
// so FileBrowser's own 2-hour token cannot outlive the dashboard session. `admin`'s token still passes.
|
||||
// A files cookie that no longer opens (the dashboard session ended, the dashboard password changed) → both cookies
|
||||
// cleared and the browser sent back to the dashboard, which asks for the sign-in again.
|
||||
//
|
||||
@@ -184,6 +187,50 @@ func (s *Server) filesCookieKey(r *http.Request) (present, ok bool) {
|
||||
return true, s.filesSessionAlive(parts[0])
|
||||
}
|
||||
|
||||
// carriesNonAdminFileBrowserToken: does the request carry a FileBrowser token (its cookie, a Bearer header, or the
|
||||
// `auth` query parameter of the forwarded URI) whose permissions are not admin's? FileBrowser 1.5.6's token is an HS256
|
||||
// JWT with the claims belongsTo, Permissions{admin,…}, iat, exp, iss (measured on demo-hp 2026-10-19); the user's NAME
|
||||
// is not in it, so admin-or-not is the line: the box has `admin` (password) and the header user (never admin,
|
||||
// infra.fileBrowserDefaultPermissions). The signature is NOT checked here and need not be: this check can only REFUSE,
|
||||
// and a token forged to read admin:true is refused by FileBrowser itself. A token that does not parse passes on to
|
||||
// FileBrowser, which refuses it. Pinned by TestFilesSSO_R1155_HouseholdTokenDiesWithTheSignOut.
|
||||
func carriesNonAdminFileBrowserToken(r *http.Request, uri string) bool {
|
||||
var toks []string
|
||||
if c, err := r.Cookie(fileBrowserJWTCookie); err == nil && c.Value != "" {
|
||||
toks = append(toks, c.Value)
|
||||
}
|
||||
if a := r.Header.Get("Authorization"); len(a) > 7 && strings.EqualFold(a[:7], "bearer ") {
|
||||
toks = append(toks, strings.TrimSpace(a[7:]))
|
||||
}
|
||||
if u, err := url.Parse(uri); err == nil {
|
||||
if v := u.Query().Get("auth"); v != "" {
|
||||
toks = append(toks, v)
|
||||
}
|
||||
}
|
||||
for _, t := range toks {
|
||||
parts := strings.Split(t, ".")
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
raw, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(parts[1], "="))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var claims struct {
|
||||
Permissions *struct {
|
||||
Admin bool `json:"admin"`
|
||||
} `json:"Permissions"`
|
||||
}
|
||||
if json.Unmarshal(raw, &claims) != nil || claims.Permissions == nil {
|
||||
continue
|
||||
}
|
||||
if !claims.Permissions.Admin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// safeFilesPath accepts a FileBrowser path to go to after the pass: "/" or under /files/ — never another host, and never
|
||||
// a dot-segment that a browser would resolve out of /files/ (onto FileBrowser's API, say).
|
||||
func safeFilesPath(p string) string {
|
||||
@@ -322,9 +369,12 @@ func (s *Server) ServeFilesAuth(w http.ResponseWriter, r *http.Request) {
|
||||
case ok:
|
||||
w.Header().Set(header, infra.FileBrowserSSOUser)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case !present:
|
||||
case !present && !carriesNonAdminFileBrowserToken(r, uri):
|
||||
w.WriteHeader(http.StatusOK) // FileBrowser's own sign-in (traefik has removed any client copy of the header)
|
||||
default:
|
||||
if !present {
|
||||
s.logger.Printf("[WARN] [web] files sign-in: a non-admin FileBrowser token came with no files cookie — refused (R-1155), visitor %s", clientIP(r))
|
||||
}
|
||||
s.clearFilesCookies(w)
|
||||
if browser {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
|
||||
Reference in New Issue
Block a user