feat(account): make the portal the single place to edit name and email #147

Sammanfogat
supernaut sammanfogade 1 incheckning från feat/identity-profile-panel in i main 2026-08-02 10:03:25 +00:00
Ägare

Turns the account panel from a read-only mirror into the one place a user edits
their name and email (ADR 0038).

Why the panel had to change

The panel rendered name and email from the id_token, which is minted at login.
So a change made anywhere else stayed invisible here until the user signed out
and back in — a stale copy of data the page did not own. Meanwhile the Git
application offered editable copies of both that propagated nowhere.

Display name

Written straight to Gitborg Auth, then projected onto the Git account. The
session cookie is re-minted on success so the panel reflects the new value
immediately rather than at next sign-in — the same pattern /api/renovate
already uses, and for the same reason.

Empty is refused rather than passed through: an absent attribute is read
downstream as "unknown" and skipped, so an empty write would look accepted and
then quietly do nothing.

Email — the part worth the most review

Gitborg Auth has no mail sender, so it cannot verify an address. The portal owns
the confirmation loop instead. Since this is also the account-recovery address,
the flow is deliberately conservative:

  • the confirmation link goes only to the proposed address, so clicking it
    proves control of the mailbox being moved to;
  • only the token's SHA-256 hash is stored — a database dump should not hand
    over working links;
  • requests expire in two hours and are single-use; a new request supersedes
    any still open, so the address that sticks is the change most recently asked
    for rather than whichever link was clicked last;
  • the token is consumed before the write, so a successful change can never
    leave a usable token behind;
  • unknown, spent, expired and wrong-account tokens all return one
    indistinguishable answer
    , so a stray link cannot be probed;
  • confirming requires the session as well as the token — a forwarded link does
    nothing for someone else;
  • the previous address is notified afterwards. That is the detection path if
    a session is ever stolen, and the message says what it means and what to do.

Sending is rate-limited per client: without a cap, an authenticated account is a
free relay for mailing arbitrary addresses.

Username

Presented as fixed, with an explanation. It is the owner segment of every
repository URL the account holds, so changing it moves everything the user owns
and breaks links in workflows and documentation. Renaming is an operator action.

Signposting

The outward links now point at the exact pages that own what is not here —
sign-in and passkeys, SSH/GPG keys, access tokens — rather than at front doors.

Also

Both endpoints act on the session subject only, never a client-supplied
identity. Guide and FAQ updated in both languages; Swedish domain terms taken
from Forgejo's sv-SE locale.

Two things to know before merging

  • Migration 0005 collides with the one on refactor/sign-up (#146).
    Whichever merges second needs regenerating, and src/db/schema.ts will
    conflict between the two branches.
  • The email flow needs the mail sender configured to be useful; without it the
    request reports that it could not send rather than claiming success.

197 tests, lint, typecheck and build clean.

Turns the account panel from a read-only mirror into the one place a user edits their name and email (ADR 0038). ## Why the panel had to change The panel rendered name and email from the `id_token`, which is minted at login. So a change made anywhere else stayed invisible here until the user signed out and back in — a stale copy of data the page did not own. Meanwhile the Git application offered editable copies of both that propagated nowhere. ## Display name Written straight to Gitborg Auth, then projected onto the Git account. The session cookie is re-minted on success so the panel reflects the new value immediately rather than at next sign-in — the same pattern `/api/renovate` already uses, and for the same reason. Empty is refused rather than passed through: an absent attribute is read downstream as "unknown" and skipped, so an empty write would look accepted and then quietly do nothing. ## Email — the part worth the most review Gitborg Auth has no mail sender, so it cannot verify an address. The portal owns the confirmation loop instead. Since this is also the account-recovery address, the flow is deliberately conservative: - the confirmation link goes **only** to the proposed address, so clicking it proves control of the mailbox being moved to; - only the token's SHA-256 **hash** is stored — a database dump should not hand over working links; - requests expire in two hours and are **single-use**; a new request supersedes any still open, so the address that sticks is the change most recently asked for rather than whichever link was clicked last; - the token is **consumed before** the write, so a successful change can never leave a usable token behind; - unknown, spent, expired and wrong-account tokens all return **one indistinguishable answer**, so a stray link cannot be probed; - confirming requires the session as well as the token — a forwarded link does nothing for someone else; - the **previous address is notified afterwards**. That is the detection path if a session is ever stolen, and the message says what it means and what to do. Sending is rate-limited per client: without a cap, an authenticated account is a free relay for mailing arbitrary addresses. ## Username Presented as fixed, with an explanation. It is the owner segment of every repository URL the account holds, so changing it moves everything the user owns and breaks links in workflows and documentation. Renaming is an operator action. ## Signposting The outward links now point at the exact pages that own what is *not* here — sign-in and passkeys, SSH/GPG keys, access tokens — rather than at front doors. ## Also Both endpoints act on the session subject only, never a client-supplied identity. Guide and FAQ updated in both languages; Swedish domain terms taken from Forgejo's `sv-SE` locale. ## Two things to know before merging - **Migration `0005` collides** with the one on `refactor/sign-up` (#146). Whichever merges second needs regenerating, and `src/db/schema.ts` will conflict between the two branches. - The email flow needs the mail sender configured to be useful; without it the request reports that it could not send rather than claiming success. 197 tests, lint, typecheck and build clean.
supernaut lade till 1 incheckning 2026-08-02 07:13:24 +00:00
feat(account): make the portal the single place to edit name and email
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m21s
ebb2748f7d
The panel showed name and email read-only, rendered from the id_token minted at
login, while Forgejo offered editable copies of both that propagated nowhere.
ADR 0038 makes Kanidm the data owner and the portal the one edit surface.

Display name is written straight to Kanidm and the session is re-minted so the
panel reflects it immediately rather than at the next sign-in.

Email goes through a confirmation loop, because Kanidm has no mail sender and
so cannot verify an address. A request stores only the token's SHA-256 hash,
expires in two hours, supersedes any request still open for the account, and is
consumed before the Kanidm write so a successful change can never leave a
usable token behind. The previous address is notified afterwards — that is the
detection path if a session is ever stolen. Unknown, spent, expired and
wrong-account tokens all return one indistinguishable answer.

Username is presented as fixed: it is the owner segment of every repository URL
the account holds, so renaming is an operator action (runbook), not
self-service.

The outward links now point at the exact pages that own what is not here —
Kanidm for sign-in, Forgejo for SSH/GPG keys and access tokens — rather than at
front doors.

Both endpoints act on the session subject only, never a client-supplied
identity, following the pattern /api/renovate already established.

Guide and FAQ updated in both languages; Swedish domain terms taken from
Forgejo's sv-SE locale.
supernaut sammanfogade incheckning fbcca2bd56 till main 2026-08-02 10:03:25 +00:00
supernaut tog bort grenen feat/identity-profile-panel 2026-08-02 10:03:25 +00:00
Logga in för att delta i denna konversation.
Inga granskare
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-web!147
Ingen beskrivning angiven.