The face from the provider, and whose business a password is

The password tab was hidden for accounts with no password, which is not the
same as accounts that sign in through the provider — an account linked to one
kept its password and therefore kept the tab. That is precisely the account
the person setting this up is using. It goes by "signs in over there" now; the
password it kept is the emergency door, set by an administrator.

And the picture. The provider sends one, the server fetches it at each
sign-in and stores it encrypted in a table beside the users rather than in
them, and serves it back through the API — so the page never talks to the
provider, no third-party cookie is needed for a face to appear, and it
survives the provider being down like everything else here. Fetched once per
account into an object URL, since an img tag cannot carry a bearer token.

The suite's provider now serves a picture too, and the check asks the browser
whether it actually decoded rather than whether the element exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-21 18:30:58 +02:00
1 parent 40bdf81aa2
commit a2b3f91e00
12 files changed
+325 -22

No files matched your search

+24 -6
View File
@@ -310,11 +310,29 @@ administrator — PocketID, Authentik, Keycloak as they are normally run — tha
exactly the intent. Do not point this at one where people choose their own name
freely.
**Both doors stay open.** An account created by the provider has no password here
at all: the interface stops offering to change one, and the login route refuses
it exactly as it refuses a wrong password, at the same speed. An administrator
can still *set* one for such an account, and that is deliberate — it is the way
back in on the day the provider is unreachable.
**Both doors stay open, and passwords become an administrator's business.** An
account created by the provider has no password here at all: the login route
refuses one exactly as it refuses a wrong password, at the same speed. An
administrator can still *set* one — deliberately, since that is the way back in
on the day the provider is unreachable — and the button in the list says "Give a
password" rather than offering to reset something that does not exist.
An account that signs in through the provider is offered no password tab of its
own, and that includes one which had a password before it was linked. The
password it kept is the emergency door, not a second thing for its owner to
maintain; the account panel says so, and names the provider it signs in through.
The administration list marks every linked account, and says which of them have
no password here.
**The picture comes along.** If the provider sends a `picture` claim — PocketID
does — the server fetches it at each sign-in and stores it, encrypted, in a table
of its own. It is served back through the API, so nothing on the page talks to
the provider, no third-party cookie is needed for it to appear, and it keeps
working when the provider does not. The bytes are fetched once per account and
turned into an object URL, since an `<img src>` cannot carry a bearer token; the
picture then shows in the sidebar, in the account panel and beside the name in
the administration list. It arrives with the *next* sign-in: that is the one
moment this app talks to the provider at all.
**The refresh token is deliberately unused.** The provider is asked once, at
sign-in; InkNote's own token carries the session from then on, and nothing here
@@ -566,7 +584,7 @@ docker compose -f docker-compose.dev.yml up -d --build # must be running
| Suite | Checks | What it holds down |
| --- | --- | --- |
| `admin` | 15 | no sign-up anywhere; `/api/admin/*` refused for non-admins even when called directly |
| `oidc` | 26 | the whole redirect journey against a provider that signs real tokens — and the four ways it must fail: a refusal, a replayed ticket, a token signed with an unpublished key, a token addressed to another client |
| `oidc` | 39 | the whole redirect journey against a provider that signs real tokens — and the four ways it must fail: a refusal, a replayed ticket, a token signed with an unpublished key, a token addressed to another client |
| `editor` | 40 | tables and their right-click menu, task lists, note links, backlinks — and that each survives a reload |
| `search-attachments` | 12 | body-only matches, ranking, umlauts; images as blob URLs, not inline bytes |
| `history` | 10 | a large deletion is snapshotted, restoring works, restoring is itself undoable |