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

+33
View File
@@ -296,9 +296,41 @@ async function completeLogin({ code, state }) {
username: claims.preferred_username || null,
email: claims.email || null,
displayName: claims.name || claims.given_name || null,
picture: typeof claims.picture === "string" ? claims.picture : null,
};
}
/**
* The profile picture, fetched by the server rather than by the browser.
*
* Three reasons it happens here. The address is often behind the provider's own
* session, which this app's visitors may not have in a third-party context; the
* browser would have to talk to the provider on every page, which is a request
* to somewhere else on every load; and an image that lives here keeps working
* when the provider does not, like everything else in this app.
*
* Best effort in the strict sense: a slow, missing, oversized or non-image
* answer gives null and the sign-in carries on. A picture is never a reason to
* fail to let somebody in.
*/
async function fetchPicture(url, { maxBytes = 512 * 1024 } = {}) {
if (!url || !/^https?:\/\//i.test(url)) return null;
try {
const res = await fetch(url, { signal: AbortSignal.timeout(4000), redirect: "follow" });
if (!res.ok) return null;
const type = String(res.headers.get("content-type") || "").split(";")[0].trim().toLowerCase();
if (!/^image\/(png|jpeg|webp|gif)$/.test(type)) return null;
const declared = Number(res.headers.get("content-length"));
if (Number.isFinite(declared) && declared > maxBytes) return null;
const bytes = Buffer.from(await res.arrayBuffer());
// The header may have lied or been absent; the bytes are the truth.
if (!bytes.length || bytes.length > maxBytes) return null;
return { bytes, type };
} catch {
return null;
}
}
/* ─── The ticket the browser carries home ─── */
function issueTicket(payload) {
@@ -325,6 +357,7 @@ module.exports = {
describeConfiguration,
beginLogin,
completeLogin,
fetchPicture,
issueTicket,
redeemTicket,
};