feat/opnform-prod #15

Open
Tobias-Wuest wants to merge 3 commits from feat/opnform-prod into main
Owner
No description provided.
Without tls.domains traefik requests a certificate covering every host in
the router rule, so an internal *.int extra domain forces its own
_acme-challenge CNAME. Setting opnform_tls_sans (e.g. *.int.<zone>) reuses
the existing wildcard delegation instead - the pattern the hand-run stacks
on the digitalboard production hosts already use.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Newer OpnForm builds bind the OIDC state to a verifier: /api/auth/{slug}/
redirect returns state_verifier, callback.vue reads it from
sessionStorage["oidc_state_verifier:<slug>:<state>"] and replays it as the
X-OIDC-State-Verifier header - consumeState() rejects the callback without
it (400 "Invalid state", user-visible as "Sign-in could not be completed").
The intercept page dropped the verifier, so every SSO login failed on
images newer than the demo rollout. Store it exactly where the SPA looks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ -206,0 +211,4 @@
# hand-run stacks on the digitalboard production hosts).
- traefik.http.routers.{{ opnform_service_name }}.tls.domains[0].main={{ opnform_domain }}
{% for san in opnform_tls_sans %}
- traefik.http.routers.{{ opnform_service_name }}.tls.domains[0].sans={{ san }}
Owner

[Hoch] Multi-SAN-Bug: Diese Zeile schreibt in der Schleife wiederholt denselben Label-Key tls.domains[0].sans. Bei Traefiks Docker-Label-Provider gewinnt der letzte Wert – bei mehreren SANs überleben also alle außer dem letzten nicht, und das Zertifikat wird unvollständig. Traefik erwartet mehrere SANs als kommaseparierten Wert in einem einzelnen Label. Vorschlag: Schleife entfernen und stattdessen - traefik.http.routers.{{ opnform_service_name }}.tls.domains[0].sans={{ opnform_tls_sans | join(',') }} verwenden.

**[Hoch] Multi-SAN-Bug:** Diese Zeile schreibt in der Schleife wiederholt denselben Label-Key `tls.domains[0].sans`. Bei Traefiks Docker-Label-Provider gewinnt der letzte Wert – bei mehreren SANs überleben also alle außer dem letzten nicht, und das Zertifikat wird unvollständig. Traefik erwartet mehrere SANs als kommaseparierten Wert in einem einzelnen Label. Vorschlag: Schleife entfernen und stattdessen `- traefik.http.routers.{{ opnform_service_name }}.tls.domains[0].sans={{ opnform_tls_sans | join(',') }}` verwenden.
@ -46,3 +51,3 @@
}
default_type text/html;
return 200 '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Redirecting to sign-in…</title></head><body style="font-family:sans-serif;text-align:center;padding:3rem;color:#374151"><p id="m">Redirecting to sign-in…</p><script>fetch("/api/auth/{{ opnform_oidc_slug }}/redirect",{method:"POST",headers:{Accept:"application/json"}}).then(function(r){if(!r.ok)throw new Error("HTTP "+r.status);return r.json()}).then(function(d){if(d&&d.redirect_url){window.location.replace(d.redirect_url)}else{throw new Error("no redirect_url")}}).catch(function(e){document.getElementById("m").textContent="Sign-in redirect failed: "+e.message+". Open /login?bypass=1 to use the email form.";});</script></body></html>';
return 200 '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Redirecting to sign-in…</title></head><body style="font-family:sans-serif;text-align:center;padding:3rem;color:#374151"><p id="m">Redirecting to sign-in…</p><script>fetch("/api/auth/{{ opnform_oidc_slug }}/redirect",{method:"POST",headers:{Accept:"application/json"}}).then(function(r){if(!r.ok)throw new Error("HTTP "+r.status);return r.json()}).then(function(d){if(d&&d.redirect_url){if(d.state&&d.state_verifier){try{sessionStorage.setItem("oidc_state_verifier:{{ opnform_oidc_slug }}:"+d.state,d.state_verifier)}catch(e){}}window.location.replace(d.redirect_url)}else{throw new Error("no redirect_url")}}).catch(function(e){document.getElementById("m").textContent="Sign-in redirect failed: "+e.message+". Open /login?bypass=1 to use the email form.";});</script></body></html>';
Owner

[Mittel] Verschluckter sessionStorage-Fehler: Der try{...}catch(e){} speichert den state_verifier still und ignoriert jeden Fehler. Ist sessionStorage nicht verfügbar (Privatmodus, deaktivierter Storage, Quota), läuft die Weiterleitung zum IdP trotzdem durch und der Callback scheitert später mit genau dem 400 „Invalid state", vor dem der Kommentar oben warnt – ohne jede Diagnose. Vorschlag: Fehlerfall dem Nutzer sichtbar machen (analog zum vorhandenen .catch), statt still fortzufahren. Zusätzlich: Diese Zeile hängt an einem internen, undokumentierten UI-Vertrag (sessionStorage-Key-Format) – ein OpnForm-Upgrade kann den Login ohne Testabdeckung brechen; bitte OpnForm-Version pinnen oder Hinweis ergänzen.

**[Mittel] Verschluckter sessionStorage-Fehler:** Der `try{...}catch(e){}` speichert den `state_verifier` still und ignoriert jeden Fehler. Ist `sessionStorage` nicht verfügbar (Privatmodus, deaktivierter Storage, Quota), läuft die Weiterleitung zum IdP trotzdem durch und der Callback scheitert später mit genau dem 400 „Invalid state", vor dem der Kommentar oben warnt – ohne jede Diagnose. Vorschlag: Fehlerfall dem Nutzer sichtbar machen (analog zum vorhandenen `.catch`), statt still fortzufahren. Zusätzlich: Diese Zeile hängt an einem internen, undokumentierten UI-Vertrag (sessionStorage-Key-Format) – ein OpnForm-Upgrade kann den Login ohne Testabdeckung brechen; bitte OpnForm-Version pinnen oder Hinweis ergänzen.
- traefik docker labels: a repeated tls.domains[0].sans key overwrites
  itself (last one wins), so multi-SAN certs silently lost all but the
  last SAN. All SANs now go into one comma-separated label.
- SSO entrypoint: a failing sessionStorage.setItem was swallowed and the
  browser was redirected into a guaranteed 400 Invalid-state callback.
  The failure now aborts visibly through the existing error handler
  (with the bypass hint) before any redirect happens.
- The entrypoint depends on OpnForm's internal sessionStorage contract,
  so api/client images are now digest-pinned to the state verified on
  the digitalboard production host; upgrading = bump digests + retest
  the SSO login.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/opnform-prod:feat/opnform-prod
git switch feat/opnform-prod

Merge

Merge the changes and update on Forgejo.
git switch main
git merge --no-ff feat/opnform-prod
git switch feat/opnform-prod
git rebase main
git switch main
git merge --ff-only feat/opnform-prod
git switch feat/opnform-prod
git rebase main
git switch main
git merge --no-ff feat/opnform-prod
git switch main
git merge --squash feat/opnform-prod
git switch main
git merge --ff-only feat/opnform-prod
git switch main
git merge feat/opnform-prod
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
souveredu/digitalboard.core!15
No description provided.