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:
@@ -1,3 +1,31 @@
|
|||||||
|
## v0.333.0 — the file manager's own pass ends with the dashboard sign-out (R-1155); the recovery-code page says the code becomes live after the check (R-1161) (2026-10-19)
|
||||||
|
|
||||||
|
**MinAgent: 0.158.1** — the floor's, carried. Neither change needs a new agent (R-1161's two texts are chosen by the
|
||||||
|
claim answer the agent already gives). No golden (`09` §3 decision 250). Task: the second day brief of 2026-10-19.
|
||||||
|
|
||||||
|
- **FileBrowser's own token no longer outlives the dashboard session (R-1155).** Measured on demo-hp before the fix: the
|
||||||
|
one-sign-in user's FileBrowser token (HS256 JWT; claims `belongsTo`, `Permissions{admin,…}`, `iat`, `exp` 2 h, `iss`
|
||||||
|
— no user name) still opened `/api/users?id=self` (200) after the dashboard sign-out, as a cookie and as a Bearer
|
||||||
|
header. The forwardAuth answer (`ServeFilesAuth`) now refuses a request that has NO valid files cookie but carries a
|
||||||
|
NON-admin FileBrowser token — in the `filebrowser_quantum_jwt` cookie, an `Authorization: Bearer` header or the
|
||||||
|
`auth` query parameter — exactly like a stale files cookie: both cookies cleared, a browser sent to the dashboard
|
||||||
|
(`/__files/open`, which re-issues the pass while the session lives), anything else 401. `admin`'s token (the password
|
||||||
|
fallback) and FileBrowser's sign-in page still pass; an unreadable token passes on to FileBrowser, which refuses it.
|
||||||
|
The signature is not checked here and need not be: this check can only refuse. `carriesNonAdminFileBrowserToken`;
|
||||||
|
`TestFilesSSO_R1155_HouseholdTokenDiesWithTheSignOut` — red-proved both ways: without the check the household's
|
||||||
|
token alone passes after the sign-out (200); with every token refused, admin's own token gets 302.
|
||||||
|
- **The recovery-code page tells the truth about when the code is live (R-1161).** Since R-1151 (agent ≥ 0.160.0) the
|
||||||
|
new code becomes live only when the two words come back; the page still said „Ez mostantól az élő helyreállítási
|
||||||
|
kód". It now shows „Ez a kód az ellenőrzés után lesz az élő helyreállítási kód: írd be lent a két kért szót. Addig a
|
||||||
|
korábbi kód marad érvényes, akkor is, ha most bezárod az oldalt. …" (key `backups_escrow.ez_a_kod_az_ellenorzes`,
|
||||||
|
English twin), and the old sentence only when the claim answer says no confirm is required (an agent before 0.160.0,
|
||||||
|
which stores the package at the claim). i18n parity: `escrow_first` and `escrow_receremony` re-captured (the
|
||||||
|
reason: R-1151 made the old sentence untrue); every other fixture byte-identical. `TestEscrowTemplates_Render`
|
||||||
|
pins both texts and the switch — red-proved with the old template.
|
||||||
|
- **The Chrome „Install" chip on the file manager: NOT-A-FINDING.** FileBrowser 1.5.6 always links
|
||||||
|
`/public/static/site.webmanifest` (display `standalone`); its only PWA setting is `disablePWAInstall` (set — it hides
|
||||||
|
FileBrowser's own prompt). No setting stops the manifest; FileBrowser is not patched.
|
||||||
|
|
||||||
## v0.332.0 — one sign-in for the file manager (R-1100, operator D16 = yes); the new recovery code replaces the old one only after the household typed it back (R-1151) (2026-10-19)
|
## v0.332.0 — one sign-in for the file manager (R-1100, operator D16 = yes); the new recovery code replaces the old one only after the household typed it back (R-1151) (2026-10-19)
|
||||||
|
|
||||||
**MinAgent: 0.158.1** — the floor's, carried. Neither change needs a new agent: the file manager's sign-in is the
|
**MinAgent: 0.158.1** — the floor's, carried. Neither change needs a new agent: the file manager's sign-in is the
|
||||||
|
|||||||
@@ -388,7 +388,9 @@ forwardAuth on `files.<domain>` (`GET /__felhom_gate/files`, `web/files_sso.go`)
|
|||||||
host-only `felhom_files` cookie and then sets the box's SECRET header (`X-Felhom-User-<32 hex>`, `<data>/filebrowser-sso-header`)
|
host-only `felhom_files` cookie and then sets the box's SECRET header (`X-Felhom-User-<32 hex>`, `<data>/filebrowser-sso-header`)
|
||||||
naming FileBrowser user `felhom-household` — FileBrowser opens at the folder with no login. No files cookie → no header:
|
naming FileBrowser user `felhom-household` — FileBrowser opens at the folder with no login. No files cookie → no header:
|
||||||
FileBrowser's own password (`admin`, the fallback). A dashboard sign-out or password change ends the files cookie at the
|
FileBrowser's own password (`admin`, the fallback). A dashboard sign-out or password change ends the files cookie at the
|
||||||
next request. The dashboard cookie stays host-only.
|
next request; since v0.333.0 (R-1155) so does FileBrowser's own 2-hour token for the household — with no valid files
|
||||||
|
cookie, a request carrying a non-admin FileBrowser token (cookie, Bearer or `?auth=`) is refused like a stale files
|
||||||
|
cookie, while `admin`'s token still passes. The dashboard cookie stays host-only.
|
||||||
|
|
||||||
#### „Még egy lépés" — a value asked AFTER the app starts (v0.315.0)
|
#### „Még egy lépés" — a value asked AFTER the app starts (v0.315.0)
|
||||||
|
|
||||||
|
|||||||
@@ -382,6 +382,7 @@
|
|||||||
"backups_escrow.elrejtes": "Hide",
|
"backups_escrow.elrejtes": "Hide",
|
||||||
"backups_escrow.es": " and no.",
|
"backups_escrow.es": " and no.",
|
||||||
"backups_escrow.ez_mostantol_az_elo_helyreallitasi": "This is now the live recovery code — the earlier code is no longer valid. Save it now: the code cannot be shown again.",
|
"backups_escrow.ez_mostantol_az_elo_helyreallitasi": "This is now the live recovery code — the earlier code is no longer valid. Save it now: the code cannot be shown again.",
|
||||||
|
"backups_escrow.ez_a_kod_az_ellenorzes": "This code becomes the live recovery code after the check: type the two requested words below. Until then the earlier code stays valid, also if you close this page now. Save it now: the code cannot be shown again.",
|
||||||
"backups_escrow.felirtam_a_kodot_es_biztonsagos": "I wrote down the code and keep it in a safe place — not on this server.",
|
"backups_escrow.felirtam_a_kodot_es_biztonsagos": "I wrote down the code and keep it in a safe place — not on this server.",
|
||||||
"backups_escrow.helyreallitasi_dr_szint_aktiv": "Recovery (DR) level active",
|
"backups_escrow.helyreallitasi_dr_szint_aktiv": "Recovery (DR) level active",
|
||||||
"backups_escrow.helyreallitasi_kod": "Recovery code",
|
"backups_escrow.helyreallitasi_kod": "Recovery code",
|
||||||
|
|||||||
@@ -378,6 +378,7 @@
|
|||||||
"backups_escrow.elrejtes": "Elrejtés",
|
"backups_escrow.elrejtes": "Elrejtés",
|
||||||
"backups_escrow.es": ". és",
|
"backups_escrow.es": ". és",
|
||||||
"backups_escrow.ez_mostantol_az_elo_helyreallitasi": "Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.",
|
"backups_escrow.ez_mostantol_az_elo_helyreallitasi": "Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.",
|
||||||
|
"backups_escrow.ez_a_kod_az_ellenorzes": "Ez a kód az ellenőrzés után lesz az élő helyreállítási kód: írd be lent a két kért szót. Addig a korábbi kód marad érvényes, akkor is, ha most bezárod az oldalt. Mentsd el most: a kód többé nem jelenik meg.",
|
||||||
"backups_escrow.felirtam_a_kodot_es_biztonsagos": "Felírtam a kódot, és biztonságos helyen — nem ezen a szerveren — tárolom.",
|
"backups_escrow.felirtam_a_kodot_es_biztonsagos": "Felírtam a kódot, és biztonságos helyen — nem ezen a szerveren — tárolom.",
|
||||||
"backups_escrow.helyreallitasi_dr_szint_aktiv": "Helyreállítási (DR) szint aktív",
|
"backups_escrow.helyreallitasi_dr_szint_aktiv": "Helyreállítási (DR) szint aktív",
|
||||||
"backups_escrow.helyreallitasi_kod": "Helyreállítási kód",
|
"backups_escrow.helyreallitasi_kod": "Helyreállítási kód",
|
||||||
|
|||||||
@@ -373,9 +373,12 @@ func TestEscrowTemplates_Render(t *testing.T) {
|
|||||||
"A folytatáshoz add meg a bejelentkezési jelszavadat",
|
"A folytatáshoz add meg a bejelentkezési jelszavadat",
|
||||||
"Kód létrehozása",
|
"Kód létrehozása",
|
||||||
"Kód megjelenítése",
|
"Kód megjelenítése",
|
||||||
// v0.127.3 (Viktor): the reveal states EXPLICITLY that the shown code is already the
|
// R-1161 (2026-10-19): with an agent >= 0.160.0 the code becomes live only after the two words come back
|
||||||
// live one (the supersede happened at upload, before display).
|
// (R-1151), so the reveal says that by default; v0.127.3's "already live" sentence is shown only for an
|
||||||
"Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.",
|
// older agent (confirm_required false), which still stores the package at the claim.
|
||||||
|
`id="live-after-check" style="margin-bottom:.5rem">Ez a kód az ellenőrzés után lesz az élő helyreállítási kód: írd be lent a két kért szót. Addig a korábbi kód marad érvényes, akkor is, ha most bezárod az oldalt. Mentsd el most: a kód többé nem jelenik meg.`,
|
||||||
|
`id="live-now" style="display:none;margin-bottom:.5rem">Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.`,
|
||||||
|
"if(!confirmRequired){ document.getElementById('live-after-check').style.display='none'; document.getElementById('live-now').style.display='block'; }",
|
||||||
// The pre-generation cancel must stay next to the start button (nothing runs until then).
|
// The pre-generation cancel must stay next to the start button (nothing runs until then).
|
||||||
`>Mégsem</a>`,
|
`>Mégsem</a>`,
|
||||||
"nem ezen a szerveren",
|
"nem ezen a szerveren",
|
||||||
|
|||||||
@@ -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
|
// 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
|
// 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).
|
// 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
|
// 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.
|
// 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])
|
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
|
// 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).
|
// a dot-segment that a browser would resolve out of /files/ (onto FileBrowser's API, say).
|
||||||
func safeFilesPath(p string) string {
|
func safeFilesPath(p string) string {
|
||||||
@@ -322,9 +369,12 @@ func (s *Server) ServeFilesAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
case ok:
|
case ok:
|
||||||
w.Header().Set(header, infra.FileBrowserSSOUser)
|
w.Header().Set(header, infra.FileBrowserSSOUser)
|
||||||
w.WriteHeader(http.StatusOK)
|
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)
|
w.WriteHeader(http.StatusOK) // FileBrowser's own sign-in (traefik has removed any client copy of the header)
|
||||||
default:
|
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)
|
s.clearFilesCookies(w)
|
||||||
if browser {
|
if browser {
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -381,3 +383,68 @@ func TestFilesSSO_R1100_CrossSiteNoPass(t *testing.T) {
|
|||||||
t.Fatal("a name with two dots inside it is a real folder name")
|
t.Fatal("a name with two dots inside it is a real folder name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fbToken is a FileBrowser 1.5.6 token's shape (claims measured on demo-hp 2026-10-19: belongsTo, Permissions{…}, iat,
|
||||||
|
// exp, iss; HS256). The signature is junk: the gate never checks it, FileBrowser does.
|
||||||
|
func fbToken(admin bool, id int) string {
|
||||||
|
enc := func(v any) string { b, _ := json.Marshal(v); return base64.RawURLEncoding.EncodeToString(b) }
|
||||||
|
return enc(map[string]any{"alg": "HS256", "typ": "JWT"}) + "." + enc(map[string]any{
|
||||||
|
"belongsTo": id, "iat": 1, "exp": 4102444800, "iss": "FileBrowser Quantum",
|
||||||
|
"Permissions": map[string]bool{"admin": admin, "api": admin, "modify": true, "share": true, "realtime": true, "delete": true, "create": true, "download": true},
|
||||||
|
}) + ".c2lnbmF0dXJl"
|
||||||
|
}
|
||||||
|
|
||||||
|
// R-1155: FileBrowser's own 2-hour token for the household user must not outlive the dashboard session. A browser that
|
||||||
|
// lost only the files cookie (or a token copied out of it) is refused like a stale files cookie — by cookie, by Bearer
|
||||||
|
// header and by the `auth` query parameter; `admin`'s token and FileBrowser's own sign-in page still pass.
|
||||||
|
func TestFilesSSO_R1155_HouseholdTokenDiesWithTheSignOut(t *testing.T) {
|
||||||
|
s := newFilesServer(t)
|
||||||
|
sess := sessionCookie(s)
|
||||||
|
fc := signIn(t, s, sess, "/")
|
||||||
|
header := s.filesHeader()
|
||||||
|
household := &http.Cookie{Name: fileBrowserJWTCookie, Value: fbToken(false, 3)}
|
||||||
|
admin := &http.Cookie{Name: fileBrowserJWTCookie, Value: fbToken(true, 1)}
|
||||||
|
|
||||||
|
if w := filesAsk(s, "files.example.hu", "/files/", htmlAccept, fc, household); w.Code != http.StatusOK || w.Header().Get(header) != infra.FileBrowserSSOUser {
|
||||||
|
t.Fatalf("signed in, files cookie + the household's token: %d header=%q, want 200 with the household", w.Code, w.Header().Get(header))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The household signs out on the dashboard; the browser kept FileBrowser's token but not the files cookie.
|
||||||
|
s.sessionsMu.Lock()
|
||||||
|
delete(s.sessions, sessionFingerprint(sess.Value))
|
||||||
|
s.sessionsMu.Unlock()
|
||||||
|
w := filesAsk(s, "files.example.hu", "/files/", htmlAccept, household)
|
||||||
|
if w.Code == http.StatusOK {
|
||||||
|
t.Fatalf("after the sign-out the household's FileBrowser token alone still passed (200) — it outlives the session")
|
||||||
|
}
|
||||||
|
if loc := w.Header().Get("Location"); w.Code != http.StatusFound || !strings.HasPrefix(loc, "https://felhom.example.hu"+filesOpenPath) {
|
||||||
|
t.Fatalf("browser with the token alone: %d → %q, want 302 to the dashboard", w.Code, loc)
|
||||||
|
}
|
||||||
|
if c := filesCookieIn(w, fileBrowserJWTCookie); c == nil || c.MaxAge >= 0 {
|
||||||
|
t.Fatal("the household's FileBrowser token was not cleared")
|
||||||
|
}
|
||||||
|
if api := filesAsk(s, "files.example.hu", "/api/users?id=self", "application/json", household); api.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("API call with the household's token cookie alone: %d, want 401", api.Code)
|
||||||
|
}
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "http://felhom-controller:8080"+filesAuthPath, nil)
|
||||||
|
r.Header.Set("X-Forwarded-Host", "files.example.hu")
|
||||||
|
r.Header.Set("X-Forwarded-Uri", "/api/users?id=self")
|
||||||
|
r.Header.Set("Authorization", "Bearer "+household.Value)
|
||||||
|
if b := s.filesEntry(r); b.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("API call with the household's token as Bearer: %d, want 401", b.Code)
|
||||||
|
}
|
||||||
|
if q := filesAsk(s, "files.example.hu", "/api/raw?files=x&auth="+url.QueryEscape(household.Value), "*/*"); q.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("download with the household's token in ?auth=: %d, want 401", q.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The fallback stays: admin's token, FileBrowser's own sign-in page, and a token FileBrowser itself will refuse.
|
||||||
|
if a := filesAsk(s, "files.example.hu", "/files/", htmlAccept, admin); a.Code != http.StatusOK || a.Header().Get(header) != "" {
|
||||||
|
t.Fatalf("admin's own token: %d header=%q, want 200 without the household", a.Code, a.Header().Get(header))
|
||||||
|
}
|
||||||
|
if p := filesAsk(s, "files.example.hu", "/login", htmlAccept); p.Code != http.StatusOK {
|
||||||
|
t.Fatalf("FileBrowser's sign-in page with no token: %d, want 200", p.Code)
|
||||||
|
}
|
||||||
|
if g := filesAsk(s, "files.example.hu", "/files/", htmlAccept, &http.Cookie{Name: fileBrowserJWTCookie, Value: "not-a-token"}); g.Code != http.StatusOK {
|
||||||
|
t.Fatalf("an unreadable token: %d, want 200 (FileBrowser refuses it itself)", g.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -85,7 +85,8 @@
|
|||||||
<div id="reveal-error" class="alert alert-error" style="display:none;margin-top:.75rem"></div>
|
<div id="reveal-error" class="alert alert-error" style="display:none;margin-top:.75rem"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="reveal-shown" style="display:none">
|
<div id="reveal-shown" style="display:none">
|
||||||
<div class="alert alert-warning" style="margin-bottom:.5rem">{{T "backups_escrow.ez_mostantol_az_elo_helyreallitasi"}}</div>
|
<div class="alert alert-warning" id="live-after-check" style="margin-bottom:.5rem">{{T "backups_escrow.ez_a_kod_az_ellenorzes"}}</div>
|
||||||
|
<div class="alert alert-warning" id="live-now" style="display:none;margin-bottom:.5rem">{{T "backups_escrow.ez_mostantol_az_elo_helyreallitasi"}}</div>
|
||||||
<pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre>
|
<pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre>
|
||||||
<button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">{{T "backups_escrow.elrejtes"}}</button>
|
<button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">{{T "backups_escrow.elrejtes"}}</button>
|
||||||
<h3 style="margin-top:1.25rem">{{T "backups_escrow.6_ellenorzes"}}</h3>
|
<h3 style="margin-top:1.25rem">{{T "backups_escrow.6_ellenorzes"}}</h3>
|
||||||
@@ -258,6 +259,9 @@ async function claimCode(){
|
|||||||
var words=claimedR.split('-');
|
var words=claimedR.split('-');
|
||||||
var n=words.length;
|
var n=words.length;
|
||||||
confirmRequired=!!j.data.confirm_required;
|
confirmRequired=!!j.data.confirm_required;
|
||||||
|
// R-1161: an agent >= 0.160.0 keeps the old code until the two words come back; an older agent stored the new
|
||||||
|
// package at the claim, so for it the code is live already.
|
||||||
|
if(!confirmRequired){ document.getElementById('live-after-check').style.display='none'; document.getElementById('live-now').style.display='block'; }
|
||||||
if(confirmRequired){ verifyIdx=[j.data.verify_positions[0], j.data.verify_positions[1]]; }
|
if(confirmRequired){ verifyIdx=[j.data.verify_positions[0], j.data.verify_positions[1]]; }
|
||||||
else {
|
else {
|
||||||
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
||||||
|
|||||||
@@ -231,7 +231,8 @@
|
|||||||
<div id="reveal-error" class="alert alert-error" style="display:none;margin-top:.75rem"></div>
|
<div id="reveal-error" class="alert alert-error" style="display:none;margin-top:.75rem"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="reveal-shown" style="display:none">
|
<div id="reveal-shown" style="display:none">
|
||||||
<div class="alert alert-warning" style="margin-bottom:.5rem">Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.</div>
|
<div class="alert alert-warning" id="live-after-check" style="margin-bottom:.5rem">Ez a kód az ellenőrzés után lesz az élő helyreállítási kód: írd be lent a két kért szót. Addig a korábbi kód marad érvényes, akkor is, ha most bezárod az oldalt. Mentsd el most: a kód többé nem jelenik meg.</div>
|
||||||
|
<div class="alert alert-warning" id="live-now" style="display:none;margin-bottom:.5rem">Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.</div>
|
||||||
<pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre>
|
<pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre>
|
||||||
<button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">Elrejtés</button>
|
<button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">Elrejtés</button>
|
||||||
<h3 style="margin-top:1.25rem">6. Ellenőrzés</h3>
|
<h3 style="margin-top:1.25rem">6. Ellenőrzés</h3>
|
||||||
@@ -403,6 +404,9 @@ async function claimCode(){
|
|||||||
var words=claimedR.split('-');
|
var words=claimedR.split('-');
|
||||||
var n=words.length;
|
var n=words.length;
|
||||||
confirmRequired=!!j.data.confirm_required;
|
confirmRequired=!!j.data.confirm_required;
|
||||||
|
|
||||||
|
|
||||||
|
if(!confirmRequired){ document.getElementById('live-after-check').style.display='none'; document.getElementById('live-now').style.display='block'; }
|
||||||
if(confirmRequired){ verifyIdx=[j.data.verify_positions[0], j.data.verify_positions[1]]; }
|
if(confirmRequired){ verifyIdx=[j.data.verify_positions[0], j.data.verify_positions[1]]; }
|
||||||
else {
|
else {
|
||||||
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
||||||
|
|||||||
@@ -236,7 +236,8 @@
|
|||||||
<div id="reveal-error" class="alert alert-error" style="display:none;margin-top:.75rem"></div>
|
<div id="reveal-error" class="alert alert-error" style="display:none;margin-top:.75rem"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="reveal-shown" style="display:none">
|
<div id="reveal-shown" style="display:none">
|
||||||
<div class="alert alert-warning" style="margin-bottom:.5rem">Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.</div>
|
<div class="alert alert-warning" id="live-after-check" style="margin-bottom:.5rem">Ez a kód az ellenőrzés után lesz az élő helyreállítási kód: írd be lent a két kért szót. Addig a korábbi kód marad érvényes, akkor is, ha most bezárod az oldalt. Mentsd el most: a kód többé nem jelenik meg.</div>
|
||||||
|
<div class="alert alert-warning" id="live-now" style="display:none;margin-bottom:.5rem">Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentsd el most: a kód többé nem jelenik meg.</div>
|
||||||
<pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre>
|
<pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre>
|
||||||
<button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">Elrejtés</button>
|
<button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">Elrejtés</button>
|
||||||
<h3 style="margin-top:1.25rem">6. Ellenőrzés</h3>
|
<h3 style="margin-top:1.25rem">6. Ellenőrzés</h3>
|
||||||
@@ -408,6 +409,9 @@ async function claimCode(){
|
|||||||
var words=claimedR.split('-');
|
var words=claimedR.split('-');
|
||||||
var n=words.length;
|
var n=words.length;
|
||||||
confirmRequired=!!j.data.confirm_required;
|
confirmRequired=!!j.data.confirm_required;
|
||||||
|
|
||||||
|
|
||||||
|
if(!confirmRequired){ document.getElementById('live-after-check').style.display='none'; document.getElementById('live-now').style.display='block'; }
|
||||||
if(confirmRequired){ verifyIdx=[j.data.verify_positions[0], j.data.verify_positions[1]]; }
|
if(confirmRequired){ verifyIdx=[j.data.verify_positions[0], j.data.verify_positions[1]]; }
|
||||||
else {
|
else {
|
||||||
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
||||||
|
|||||||
Reference in New Issue
Block a user