The escrow wizard sends the typed-back words to the box; the new package reaches the hub only when they match (R-1151 controller half, unreleased; works with every agent)
gates / gates (push) Successful in 47s
gates / gates (push) Successful in 47s
This commit is contained in:
@@ -32,7 +32,8 @@ type escrowAgent interface {
|
||||
EscrowPreflight(ctx context.Context) (agentapi.EscrowPreflightResponse, error)
|
||||
EscrowCeremonyStart(ctx context.Context) (agentapi.EscrowCeremonyStartResponse, int, error)
|
||||
EscrowCeremonyStatus(ctx context.Context) (agentapi.EscrowCeremonyStatusResponse, error)
|
||||
EscrowCeremonyClaim(ctx context.Context) (string, int, error)
|
||||
EscrowCeremonyClaim(ctx context.Context) (agentapi.EscrowClaim, int, error)
|
||||
EscrowCeremonyConfirm(ctx context.Context, words []string) (int, error)
|
||||
AgentVersion() string
|
||||
}
|
||||
|
||||
@@ -102,6 +103,8 @@ func (s *Server) ServeEscrowAPI(w http.ResponseWriter, r *http.Request) {
|
||||
s.escrowStatusAPIHandler(w, r)
|
||||
case r.URL.Path == "/api/escrow/claim" && r.Method == http.MethodPost:
|
||||
s.escrowClaimAPIHandler(w, r)
|
||||
case r.URL.Path == "/api/escrow/confirm" && r.Method == http.MethodPost:
|
||||
s.escrowConfirmAPIHandler(w, r)
|
||||
default:
|
||||
escrowJSON(w, http.StatusNotFound, nil, "ismeretlen végpont")
|
||||
}
|
||||
@@ -275,7 +278,8 @@ func (s *Server) escrowClaimAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
escrowJSON(w, http.StatusBadGateway, nil, "Az ügynök nem elérhető.")
|
||||
return
|
||||
}
|
||||
code, status, err := agent.EscrowCeremonyClaim(r.Context())
|
||||
claim, status, err := agent.EscrowCeremonyClaim(r.Context())
|
||||
code := claim.Code
|
||||
if err != nil {
|
||||
switch status {
|
||||
case http.StatusGone: // 410 — the code was minted but never shown; permanently void.
|
||||
@@ -293,7 +297,63 @@ func (s *Server) escrowClaimAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if claim.ConfirmRequired {
|
||||
// R-1151: the agent keeps the new package until the household types the two words back (/api/escrow/confirm);
|
||||
// the ceremony is not complete yet, so nothing is stamped and no report is pushed here.
|
||||
s.logger.Printf("[INFO] [web] escrow recovery code claimed (one-shot; not logged) — its package waits for the typed-back words")
|
||||
escrowJSON(w, http.StatusOK, map[string]any{"recovery_code": code, "confirm_required": true, "verify_positions": claim.Positions}, "")
|
||||
code, claim = "", agentapi.EscrowClaim{}
|
||||
_, _ = code, claim
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[INFO] [web] escrow recovery code claimed (one-shot; not logged)")
|
||||
s.escrowCompleted()
|
||||
escrowJSON(w, http.StatusOK, map[string]any{"recovery_code": code}, "")
|
||||
code = "" // drop the reference promptly (GC caveat: best-effort)
|
||||
_ = code
|
||||
}
|
||||
|
||||
// escrowConfirmAPIHandler (R-1151) takes the two words the household typed back and hands them to the agent, which
|
||||
// stores the new package on the hub only when they match. Body: JSON {"words": [w1, w2]}. The words are part of the
|
||||
// code: never logged. On success the ceremony is complete (escrowCompleted).
|
||||
func (s *Server) escrowConfirmAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
var req struct {
|
||||
Words []string `json:"words"`
|
||||
}
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4096)).Decode(&req); err != nil || len(req.Words) != 2 {
|
||||
escrowJSON(w, http.StatusBadRequest, nil, s.msg(r, "api.escrow.confirm_words_mismatch"))
|
||||
return
|
||||
}
|
||||
agent, err := s.escrowAgentConn()
|
||||
if err != nil {
|
||||
escrowJSON(w, http.StatusBadGateway, nil, "Az ügynök nem elérhető.")
|
||||
return
|
||||
}
|
||||
status, err := agent.EscrowCeremonyConfirm(r.Context(), req.Words)
|
||||
req.Words = nil
|
||||
if err != nil {
|
||||
switch status {
|
||||
case http.StatusUnprocessableEntity, http.StatusBadRequest: // the words do not match — the wait goes on
|
||||
escrowJSON(w, http.StatusUnprocessableEntity, nil, s.msg(r, "api.escrow.confirm_words_mismatch"))
|
||||
case http.StatusGone:
|
||||
escrowJSON(w, http.StatusGone, map[string]any{"restart": true}, s.msg(r, "api.escrow.confirm_expired"))
|
||||
case http.StatusNotFound:
|
||||
escrowJSON(w, http.StatusGone, map[string]any{"restart": true}, s.msg(r, "api.escrow.confirm_lost"))
|
||||
default:
|
||||
s.logger.Printf("[WARN] [web] escrow confirm failed (status %d)", status)
|
||||
escrowJSON(w, http.StatusBadGateway, nil, s.msg(r, "api.escrow.confirm_store_failed"))
|
||||
}
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[INFO] [web] escrow recovery code typed back — the new package is on the hub (R-1151)")
|
||||
s.escrowCompleted()
|
||||
escrowJSON(w, http.StatusOK, map[string]any{"uploaded": true}, "")
|
||||
}
|
||||
|
||||
// escrowCompleted runs when the new package is on the hub: at the claim for an agent older than 0.160.0, at the
|
||||
// typed-back confirm since (R-1151).
|
||||
func (s *Server) escrowCompleted() {
|
||||
// v0.138.0: stamp the ceremony-completed time so /backups/remote shows the "awaiting hub
|
||||
// confirmation" card during the report-cycle gap before the auto-confirmer flips to escrowed
|
||||
// (Phase-0 verdict A: the yellow "szükséges" card during that ~15-min wait was the real gap).
|
||||
@@ -310,9 +370,6 @@ func (s *Server) escrowClaimAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// hub's ACK hash-match flip pending→escrowed in seconds (EscrowAutoConfirmer, unchanged)
|
||||
// instead of after the next ~15-min cycle. Non-blocking; a failed push degrades to the cycle.
|
||||
s.reportTriggerNow()
|
||||
escrowJSON(w, http.StatusOK, map[string]any{"recovery_code": code}, "")
|
||||
code = "" // drop the reference promptly (GC caveat: best-effort)
|
||||
_ = code
|
||||
}
|
||||
|
||||
// clearAuthFailures clears the login counter after a right re-auth password — the counter reserveLoginAttempt counts
|
||||
|
||||
@@ -35,7 +35,13 @@ type fakeEscrowAgent struct {
|
||||
claimCode string
|
||||
claimStatus int
|
||||
claimErr error
|
||||
pf agentapi.EscrowPreflightResponse
|
||||
// R-1151: an agent ≥ 0.160.0 answers the claim with a confirm request; confirmStatus/confirmErr answer the confirm.
|
||||
claimConfirm bool
|
||||
claimPos []int
|
||||
confirmWords []string
|
||||
confirmStatus int
|
||||
confirmErr error
|
||||
pf agentapi.EscrowPreflightResponse
|
||||
}
|
||||
|
||||
func (f *fakeEscrowAgent) EscrowPreflight(context.Context) (agentapi.EscrowPreflightResponse, error) {
|
||||
@@ -49,9 +55,14 @@ func (f *fakeEscrowAgent) EscrowCeremonyStart(context.Context) (agentapi.EscrowC
|
||||
func (f *fakeEscrowAgent) EscrowCeremonyStatus(context.Context) (agentapi.EscrowCeremonyStatusResponse, error) {
|
||||
return f.status, nil
|
||||
}
|
||||
func (f *fakeEscrowAgent) EscrowCeremonyClaim(context.Context) (string, int, error) {
|
||||
func (f *fakeEscrowAgent) EscrowCeremonyClaim(context.Context) (agentapi.EscrowClaim, int, error) {
|
||||
*f.order = append(*f.order, "claim")
|
||||
return f.claimCode, f.claimStatus, f.claimErr
|
||||
return agentapi.EscrowClaim{Code: f.claimCode, ConfirmRequired: f.claimConfirm, Positions: f.claimPos}, f.claimStatus, f.claimErr
|
||||
}
|
||||
func (f *fakeEscrowAgent) EscrowCeremonyConfirm(_ context.Context, words []string) (int, error) {
|
||||
*f.order = append(*f.order, "confirm")
|
||||
f.confirmWords = append([]string(nil), words...)
|
||||
return f.confirmStatus, f.confirmErr
|
||||
}
|
||||
func (f *fakeEscrowAgent) AgentVersion() string { return f.version }
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R-1151 — the wizard's typed-back words reach the agent, which stores the new package only when they match. Until
|
||||
// then the ceremony is NOT complete: no „completed" stamp, no report push — the hub still holds the previous package.
|
||||
|
||||
func postConfirm(t *testing.T, s *Server, lang, body string) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
r := httptest.NewRequest("POST", "/api/escrow/confirm?lang="+lang, strings.NewReader(body))
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
s.escrowConfirmAPIHandler(w, r)
|
||||
return w
|
||||
}
|
||||
|
||||
func ceremonyStamped(t *testing.T, h *escrowWizardHarness) bool {
|
||||
t.Helper()
|
||||
o := h.sett.GetOffboxTarget()
|
||||
return o != nil && o.CeremonyCompletedAt != ""
|
||||
}
|
||||
|
||||
// COMPANION RED-PROOF (observed 2026-10-19): drop the `if claim.ConfirmRequired { … return }` block in
|
||||
// escrowClaimAPIHandler → this fails with `claim: 200 {"data":{"recovery_code":"egy-ketto-harom-negy"},…}` — no
|
||||
// confirm request reaches the page, and the claim stamps the ceremony completed. Restored.
|
||||
func TestR1151_ClaimThatWaitsIsNotCompleteUntilTheWordsComeBack(t *testing.T) {
|
||||
h := newEscrowWizardHarness(t)
|
||||
h.configureOffbox(t, "pending")
|
||||
h.agent.claimCode, h.agent.claimStatus = "egy-ketto-harom-negy", http.StatusOK
|
||||
h.agent.claimConfirm, h.agent.claimPos = true, []int{2, 4}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
h.s.escrowClaimAPIHandler(w, httptest.NewRequest("POST", "/api/escrow/claim", nil))
|
||||
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), `"confirm_required":true`) || !strings.Contains(w.Body.String(), `"verify_positions":[2,4]`) {
|
||||
t.Fatalf("claim: %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
if ceremonyStamped(t, h) {
|
||||
t.Fatal("a claim that waits for the typed-back words stamped the ceremony completed")
|
||||
}
|
||||
|
||||
h.agent.confirmStatus = http.StatusOK
|
||||
if w := postConfirm(t, h.s, "hu", `{"words":["ketto","negy"]}`); w.Code != http.StatusOK {
|
||||
t.Fatalf("confirm: %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
if got := strings.Join(h.agent.confirmWords, ","); got != "ketto,negy" {
|
||||
t.Fatalf("the agent got the words %q, want ketto,negy", got)
|
||||
}
|
||||
if !ceremonyStamped(t, h) {
|
||||
t.Fatal("the confirmed ceremony was not stamped completed")
|
||||
}
|
||||
if c := w.Header().Get("Cache-Control"); c != "no-store" {
|
||||
t.Fatalf("confirm Cache-Control = %q", c)
|
||||
}
|
||||
}
|
||||
|
||||
// An agent older than 0.160.0 answers the claim without a confirm request: its package is on the hub, and the claim
|
||||
// completes the ceremony as before.
|
||||
func TestR1151_OldAgentClaimCompletesAtOnce(t *testing.T) {
|
||||
h := newEscrowWizardHarness(t)
|
||||
h.configureOffbox(t, "pending")
|
||||
h.agent.claimCode, h.agent.claimStatus = "egy-ketto-harom-negy", http.StatusOK
|
||||
w := httptest.NewRecorder()
|
||||
h.s.escrowClaimAPIHandler(w, httptest.NewRequest("POST", "/api/escrow/claim", nil))
|
||||
if w.Code != http.StatusOK || strings.Contains(w.Body.String(), "confirm_required") {
|
||||
t.Fatalf("claim on an old agent: %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
if !ceremonyStamped(t, h) {
|
||||
t.Fatal("an old agent's claim must complete the ceremony")
|
||||
}
|
||||
for _, o := range h.order {
|
||||
if o == "confirm" {
|
||||
t.Fatal("a confirm was sent to an old agent")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Every refusal of the confirm answers in the box's language, says the previous code still works where it does,
|
||||
// and tells the page to start again only when the new code is void.
|
||||
func TestR1151_ConfirmAnswersInTheBoxLanguage(t *testing.T) {
|
||||
cases := []struct {
|
||||
agent int
|
||||
want int
|
||||
restart bool
|
||||
hu, en string
|
||||
}{
|
||||
{http.StatusUnprocessableEntity, http.StatusUnprocessableEntity, false, "A megadott szavak nem egyeznek", "The words do not match"},
|
||||
{http.StatusGone, http.StatusGone, true, "Lejárt az idő", "has run out"},
|
||||
{http.StatusNotFound, http.StatusGone, true, "már nem vár megerősítésre", "no longer waits"},
|
||||
{http.StatusBadGateway, http.StatusBadGateway, false, "nem sikerült elmenteni", "could not be saved"},
|
||||
{0, http.StatusBadGateway, false, "nem sikerült elmenteni", "could not be saved"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
for _, lang := range []string{"hu", "en"} {
|
||||
h := newEscrowWizardHarness(t)
|
||||
h.agent.confirmStatus, h.agent.confirmErr = c.agent, fmt.Errorf("agent said %d", c.agent)
|
||||
w := postConfirm(t, h.s, lang, `{"words":["a","b"]}`)
|
||||
want := map[string]string{"hu": c.hu, "en": c.en}[lang]
|
||||
if w.Code != c.want || !strings.Contains(w.Body.String(), want) {
|
||||
t.Errorf("agent %d, %s: got %d %s — want %d with %q", c.agent, lang, w.Code, w.Body.String(), c.want, want)
|
||||
}
|
||||
if got := strings.Contains(w.Body.String(), `"restart":true`); got != c.restart {
|
||||
t.Errorf("agent %d: restart=%v, want %v", c.agent, got, c.restart)
|
||||
}
|
||||
if ceremonyStamped(t, h) {
|
||||
t.Errorf("agent %d: a refused confirm stamped the ceremony", c.agent)
|
||||
}
|
||||
}
|
||||
}
|
||||
// A body without two words never reaches the agent.
|
||||
h := newEscrowWizardHarness(t)
|
||||
if w := postConfirm(t, h.s, "hu", `{"words":["a"]}`); w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("one word: got %d", w.Code)
|
||||
}
|
||||
if len(h.order) != 0 {
|
||||
t.Fatalf("a malformed confirm reached the agent: %v", h.order)
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,7 @@
|
||||
<div id="verify-error" class="alert alert-error" style="display:none;margin-top:.5rem">{{T "backups_escrow.a_megadott_szavak_nem_egyeznek"}}</div>
|
||||
</div>
|
||||
<button class="btn btn-sm" id="verify-btn" onclick="verifyWords()">{{T "backups_escrow.szavak_ellenorzese"}}</button>
|
||||
<a href="/backup/escrow" class="btn btn-sm btn-primary" id="verify-restart" style="display:none">{{T "backups_escrow.ujrainditas"}}</a>
|
||||
<div id="finish-block" style="display:none;margin-top:1rem">
|
||||
<h3>{{T "backups_escrow.7_befejezes"}}</h3>
|
||||
{{/* v0.138.0: the "what happens next" note — the wizard finishes to /backups/remote,
|
||||
@@ -239,6 +240,9 @@ function pollStatus(){
|
||||
// finishWizard() clears both. It never leaves the page's JS scope.
|
||||
var claimedR='';
|
||||
var verifyIdx=[0,0];
|
||||
// R-1151: an agent ≥ 0.160.0 keeps the new package until the two words come back through /api/escrow/confirm, and
|
||||
// names the two positions itself; an older agent stored it at the claim, and the page picks the positions.
|
||||
var confirmRequired=false;
|
||||
|
||||
async function claimCode(){
|
||||
var btn=document.getElementById('reveal-btn'); btn.disabled=true;
|
||||
@@ -253,9 +257,13 @@ async function claimCode(){
|
||||
// distinct 1-based word positions of the dash-separated code.
|
||||
var words=claimedR.split('-');
|
||||
var n=words.length;
|
||||
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
||||
while(b===a){ b=1+Math.floor(Math.random()*n); }
|
||||
verifyIdx=[Math.min(a,b),Math.max(a,b)];
|
||||
confirmRequired=!!j.data.confirm_required;
|
||||
if(confirmRequired){ verifyIdx=[j.data.verify_positions[0], j.data.verify_positions[1]]; }
|
||||
else {
|
||||
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
||||
while(b===a){ b=1+Math.floor(Math.random()*n); }
|
||||
verifyIdx=[Math.min(a,b),Math.max(a,b)];
|
||||
}
|
||||
// Span-per-word DOM build — createElement + textContent + createTextNode ONLY. R never flows
|
||||
// through innerHTML/insertAdjacentHTML: with no HTML parse context there is no injection
|
||||
// surface, the same guarantee the old single textContent assignment gave. The two words the
|
||||
@@ -288,13 +296,31 @@ function setCodeBlur(on){
|
||||
}
|
||||
function toggleCodeBlur(){ setCodeBlur(!codeBlurred); }
|
||||
|
||||
function verifyWords(){
|
||||
async function verifyWords(){
|
||||
var words=claimedR.split('-');
|
||||
var w1=(document.getElementById('verify-w1').value||'').trim().toLowerCase();
|
||||
var w2=(document.getElementById('verify-w2').value||'').trim().toLowerCase();
|
||||
var ok = w1===words[verifyIdx[0]-1] && w2===words[verifyIdx[1]-1];
|
||||
document.getElementById('verify-error').style.display=ok?'none':'block';
|
||||
if(ok){ document.getElementById('finish-block').style.display='block'; }
|
||||
var errEl=document.getElementById('verify-error');
|
||||
errEl.textContent='{{T "backups_escrow.a_megadott_szavak_nem_egyeznek"}}';
|
||||
errEl.style.display=ok?'none':'block';
|
||||
if(!ok){ return; }
|
||||
if(confirmRequired){
|
||||
var btn=document.getElementById('verify-btn'); btn.disabled=true;
|
||||
try{
|
||||
var r=await fetch('/api/escrow/confirm',{method:'POST',
|
||||
headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({words:[w1,w2]})});
|
||||
var j=await r.json();
|
||||
if(!j.ok){
|
||||
errEl.textContent=j.error||'{{T "backups_escrow.a_megadott_szavak_nem_egyeznek"}}'; errEl.style.display='block';
|
||||
if(j.data && j.data.restart){ // the new code is void; the previous one still works — start again
|
||||
claimedR=''; document.getElementById('code-block').textContent='';
|
||||
btn.style.display='none'; document.getElementById('verify-restart').style.display='inline-block'; return; }
|
||||
btn.disabled=false; return;
|
||||
}
|
||||
}catch(e){ errEl.textContent='{{T "backups_escrow.hiba_2"}} '+e.message; errEl.style.display='block'; btn.disabled=false; return; }
|
||||
}
|
||||
document.getElementById('finish-block').style.display='block';
|
||||
}
|
||||
|
||||
function finishWizard(){
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
<div id="verify-error" class="alert alert-error" style="display:none;margin-top:.5rem">A megadott szavak nem egyeznek — ellenőrizd a felírt kódot.</div>
|
||||
</div>
|
||||
<button class="btn btn-sm" id="verify-btn" onclick="verifyWords()">Szavak ellenőrzése</button>
|
||||
<a href="/backup/escrow" class="btn btn-sm btn-primary" id="verify-restart" style="display:none">Újraindítás</a>
|
||||
<div id="finish-block" style="display:none;margin-top:1rem">
|
||||
<h3>7. Befejezés</h3>
|
||||
|
||||
@@ -385,6 +386,9 @@ function pollStatus(){
|
||||
var claimedR='';
|
||||
var verifyIdx=[0,0];
|
||||
|
||||
|
||||
var confirmRequired=false;
|
||||
|
||||
async function claimCode(){
|
||||
var btn=document.getElementById('reveal-btn'); btn.disabled=true;
|
||||
var errEl=document.getElementById('reveal-error'); errEl.style.display='none';
|
||||
@@ -398,9 +402,13 @@ async function claimCode(){
|
||||
|
||||
var words=claimedR.split('-');
|
||||
var n=words.length;
|
||||
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
||||
while(b===a){ b=1+Math.floor(Math.random()*n); }
|
||||
verifyIdx=[Math.min(a,b),Math.max(a,b)];
|
||||
confirmRequired=!!j.data.confirm_required;
|
||||
if(confirmRequired){ verifyIdx=[j.data.verify_positions[0], j.data.verify_positions[1]]; }
|
||||
else {
|
||||
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
||||
while(b===a){ b=1+Math.floor(Math.random()*n); }
|
||||
verifyIdx=[Math.min(a,b),Math.max(a,b)];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -433,13 +441,31 @@ function setCodeBlur(on){
|
||||
}
|
||||
function toggleCodeBlur(){ setCodeBlur(!codeBlurred); }
|
||||
|
||||
function verifyWords(){
|
||||
async function verifyWords(){
|
||||
var words=claimedR.split('-');
|
||||
var w1=(document.getElementById('verify-w1').value||'').trim().toLowerCase();
|
||||
var w2=(document.getElementById('verify-w2').value||'').trim().toLowerCase();
|
||||
var ok = w1===words[verifyIdx[0]-1] && w2===words[verifyIdx[1]-1];
|
||||
document.getElementById('verify-error').style.display=ok?'none':'block';
|
||||
if(ok){ document.getElementById('finish-block').style.display='block'; }
|
||||
var errEl=document.getElementById('verify-error');
|
||||
errEl.textContent='A megadott szavak nem egyeznek — ellenőrizd a felírt kódot.';
|
||||
errEl.style.display=ok?'none':'block';
|
||||
if(!ok){ return; }
|
||||
if(confirmRequired){
|
||||
var btn=document.getElementById('verify-btn'); btn.disabled=true;
|
||||
try{
|
||||
var r=await fetch('/api/escrow/confirm',{method:'POST',
|
||||
headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({words:[w1,w2]})});
|
||||
var j=await r.json();
|
||||
if(!j.ok){
|
||||
errEl.textContent=j.error||'A megadott szavak nem egyeznek — ellenőrizd a felírt kódot.'; errEl.style.display='block';
|
||||
if(j.data && j.data.restart){
|
||||
claimedR=''; document.getElementById('code-block').textContent='';
|
||||
btn.style.display='none'; document.getElementById('verify-restart').style.display='inline-block'; return; }
|
||||
btn.disabled=false; return;
|
||||
}
|
||||
}catch(e){ errEl.textContent='Hiba: '+e.message; errEl.style.display='block'; btn.disabled=false; return; }
|
||||
}
|
||||
document.getElementById('finish-block').style.display='block';
|
||||
}
|
||||
|
||||
function finishWizard(){
|
||||
|
||||
@@ -249,6 +249,7 @@
|
||||
<div id="verify-error" class="alert alert-error" style="display:none;margin-top:.5rem">A megadott szavak nem egyeznek — ellenőrizd a felírt kódot.</div>
|
||||
</div>
|
||||
<button class="btn btn-sm" id="verify-btn" onclick="verifyWords()">Szavak ellenőrzése</button>
|
||||
<a href="/backup/escrow" class="btn btn-sm btn-primary" id="verify-restart" style="display:none">Újraindítás</a>
|
||||
<div id="finish-block" style="display:none;margin-top:1rem">
|
||||
<h3>7. Befejezés</h3>
|
||||
|
||||
@@ -390,6 +391,9 @@ function pollStatus(){
|
||||
var claimedR='';
|
||||
var verifyIdx=[0,0];
|
||||
|
||||
|
||||
var confirmRequired=false;
|
||||
|
||||
async function claimCode(){
|
||||
var btn=document.getElementById('reveal-btn'); btn.disabled=true;
|
||||
var errEl=document.getElementById('reveal-error'); errEl.style.display='none';
|
||||
@@ -403,9 +407,13 @@ async function claimCode(){
|
||||
|
||||
var words=claimedR.split('-');
|
||||
var n=words.length;
|
||||
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
||||
while(b===a){ b=1+Math.floor(Math.random()*n); }
|
||||
verifyIdx=[Math.min(a,b),Math.max(a,b)];
|
||||
confirmRequired=!!j.data.confirm_required;
|
||||
if(confirmRequired){ verifyIdx=[j.data.verify_positions[0], j.data.verify_positions[1]]; }
|
||||
else {
|
||||
var a=1+Math.floor(Math.random()*n), b=1+Math.floor(Math.random()*n);
|
||||
while(b===a){ b=1+Math.floor(Math.random()*n); }
|
||||
verifyIdx=[Math.min(a,b),Math.max(a,b)];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -438,13 +446,31 @@ function setCodeBlur(on){
|
||||
}
|
||||
function toggleCodeBlur(){ setCodeBlur(!codeBlurred); }
|
||||
|
||||
function verifyWords(){
|
||||
async function verifyWords(){
|
||||
var words=claimedR.split('-');
|
||||
var w1=(document.getElementById('verify-w1').value||'').trim().toLowerCase();
|
||||
var w2=(document.getElementById('verify-w2').value||'').trim().toLowerCase();
|
||||
var ok = w1===words[verifyIdx[0]-1] && w2===words[verifyIdx[1]-1];
|
||||
document.getElementById('verify-error').style.display=ok?'none':'block';
|
||||
if(ok){ document.getElementById('finish-block').style.display='block'; }
|
||||
var errEl=document.getElementById('verify-error');
|
||||
errEl.textContent='A megadott szavak nem egyeznek — ellenőrizd a felírt kódot.';
|
||||
errEl.style.display=ok?'none':'block';
|
||||
if(!ok){ return; }
|
||||
if(confirmRequired){
|
||||
var btn=document.getElementById('verify-btn'); btn.disabled=true;
|
||||
try{
|
||||
var r=await fetch('/api/escrow/confirm',{method:'POST',
|
||||
headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({words:[w1,w2]})});
|
||||
var j=await r.json();
|
||||
if(!j.ok){
|
||||
errEl.textContent=j.error||'A megadott szavak nem egyeznek — ellenőrizd a felírt kódot.'; errEl.style.display='block';
|
||||
if(j.data && j.data.restart){
|
||||
claimedR=''; document.getElementById('code-block').textContent='';
|
||||
btn.style.display='none'; document.getElementById('verify-restart').style.display='inline-block'; return; }
|
||||
btn.disabled=false; return;
|
||||
}
|
||||
}catch(e){ errEl.textContent='Hiba: '+e.message; errEl.style.display='block'; btn.disabled=false; return; }
|
||||
}
|
||||
document.getElementById('finish-block').style.display='block';
|
||||
}
|
||||
|
||||
function finishWizard(){
|
||||
|
||||
Reference in New Issue
Block a user