Make delivery and integration failures explicit

Persist reminder presentations and retry state, atomically complete collapsed deliveries, fall back across away reaches, and block permanent failures visibly (V-715, V-678). Fail closed when enabled integrations lack credentials and keep remote arms explicitly dark (V-691). Give mavweb one sanitized, request-correlated error contract (V-689). Owner explicitly requested direct commits to master.
This commit is contained in:
2026-08-13 02:50:59 +04:00
parent da9114b623
commit 35c6ff5a71
67 changed files with 3174 additions and 477 deletions
+18 -14
View File
@@ -92,8 +92,8 @@ var passkeyTmpl = parsePage("passkey", passkeyPageHTML, nil)
func (h *PasskeyHandle) RegisterBegin(w http.ResponseWriter, r *http.Request) {
opts, challenge, err := h.rp.CreationOptions([]byte("maven-user"), "maven user")
if err != nil {
log.Printf("webauthn: register begin: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
writeProblem(w, r, http.StatusInternalServerError, problemWebAuthnBegin,
"passkey registration could not start", fmt.Errorf("webauthn register begin: %w", err))
return
}
w.Header().Set("Content-Type", "application/json")
@@ -102,7 +102,8 @@ func (h *PasskeyHandle) RegisterBegin(w http.ResponseWriter, r *http.Request) {
func (h *PasskeyHandle) RegisterFinish(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "POST only", http.StatusMethodNotAllowed)
writeProblem(w, r, http.StatusMethodNotAllowed, problemMethodNotAllowed,
"POST only", nil)
return
}
var body struct {
@@ -110,7 +111,8 @@ func (h *PasskeyHandle) RegisterFinish(w http.ResponseWriter, r *http.Request) {
Credential map[string]any `json:"credential"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "bad request: "+err.Error(), http.StatusBadRequest)
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
"invalid registration request", fmt.Errorf("decode webauthn registration: %w", err))
return
}
save := func(id string, publicKey []byte, _ []byte, _ string) error {
@@ -118,8 +120,8 @@ func (h *PasskeyHandle) RegisterFinish(w http.ResponseWriter, r *http.Request) {
}
credID, err := h.rp.FinishRegistration(save, body.Challenge, body.Credential)
if err != nil {
log.Printf("webauthn: register finish: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
writeProblem(w, r, http.StatusBadRequest, problemWebAuthnFinish,
"passkey registration failed", fmt.Errorf("webauthn register finish: %w", err))
return
}
log.Printf("webauthn: registered credential %s", credID)
@@ -140,8 +142,8 @@ func (h *PasskeyHandle) RegisterFinish(w http.ResponseWriter, r *http.Request) {
func (h *PasskeyHandle) AssertBegin(w http.ResponseWriter, r *http.Request) {
opts, challenge, err := h.rp.AssertionOptions()
if err != nil {
log.Printf("webauthn: assert begin: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
writeProblem(w, r, http.StatusInternalServerError, problemWebAuthnBegin,
"passkey assertion could not start", fmt.Errorf("webauthn assert begin: %w", err))
return
}
w.Header().Set("Content-Type", "application/json")
@@ -150,7 +152,8 @@ func (h *PasskeyHandle) AssertBegin(w http.ResponseWriter, r *http.Request) {
func (h *PasskeyHandle) AssertFinish(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "POST only", http.StatusMethodNotAllowed)
writeProblem(w, r, http.StatusMethodNotAllowed, problemMethodNotAllowed,
"POST only", nil)
return
}
var body struct {
@@ -176,7 +179,8 @@ func (h *PasskeyHandle) AssertFinish(w http.ResponseWriter, r *http.Request) {
Explicit bool `json:"explicit"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "bad request: "+err.Error(), http.StatusBadRequest)
writeProblem(w, r, http.StatusBadRequest, problemInvalidRequest,
"invalid assertion request", fmt.Errorf("decode webauthn assertion: %w", err))
return
}
@@ -189,8 +193,8 @@ func (h *PasskeyHandle) AssertFinish(w http.ResponseWriter, r *http.Request) {
credID, err := h.rp.FinishAssertion(lookup, update, body.Challenge, body.Credential)
if err != nil {
log.Printf("webauthn: assert finish: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
writeProblem(w, r, http.StatusBadRequest, problemWebAuthnFinish,
"passkey assertion failed", fmt.Errorf("webauthn assert finish: %w", err))
return
}
@@ -201,8 +205,8 @@ func (h *PasskeyHandle) AssertFinish(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
if err := h.assertFn.AssertStepUp(ctx); err != nil {
log.Printf("webauthn: assert step-up: %v", err)
http.Error(w, "step-up assertion failed", http.StatusBadGateway)
writeProblem(w, r, http.StatusBadGateway, problemWebAuthnStepUp,
"step-up assertion failed", fmt.Errorf("assert step-up in core: %w", err))
return
}
}