Summary
The name field of each entry in sso.js (list: [{ name: 'Keycloak', ... }]) looks like a cosmetic label
The README only describes it as the text shown on the “Register with SSONAME” button. In reality it is also the storage key of the SSO user database: CryptPad core stores each SSO account's block seed under
data/sso_user/<provider name>/<xx>/<oidc sub>.json
(see lib/storage/sso.js, userPathFromId()), and the plugin resolves providers with a strict string match (sso-utils.js, getProviderConfig: cfg.name === provider).
If an admin ever changes name (rebranding the login button, fixing capitalization, migrating config to a new file/repo…), every SSO user lookup misses, and CryptPad silently registers each returning user as a brand-new account with a freshly generated seed (SSOUtils.writeUser). Users log in successfully via their IdP as usual, but land in an empty drive, with no error and no hint of what happened. Their data is still on the server, unreachable, under the old provider directory.
Steps to reproduce
- Configure an OIDC provider in
sso.js with name: 'keycloak interhop'.
- Register a user through SSO, put documents in the drive.
- Change the provider to
name: 'Keycloak' (same url, client_id, everything else identical).
- Restart, log in again with the same IdP user.
Expected: either the same account, or a clear error explaining the provider is unknown.
Actual: authentication succeeds and a new, empty account is silently created (data/sso_user/Keycloak/... next to the orphaned data/sso_user/keycloak interhop/...). The user's public key changes; drive, teams and contacts appear lost.
Suggested fix
Decouple the stable identifier from the display label (backward compatible):
- Add an immutable
id field per provider, and demote name to a pure display label that admins can change freely.
- Resolve providers and build storage paths with
cfg.id || cfg.name: existing instances keep working unchanged (their sso_user/<name>/ directories remain valid), while new installations are encouraged to set a short stable id (e.g. keycloak) separate from the button text (e.g. Sign in with MyOrg).
- The decree-based admin UI already asks for a “provider id”; it only lacks the separate label field.
Complementary guard rails (each valuable on its own)
- Startup /
/checkup warning: if data/sso_user/ contains provider directories that don't match any configured provider, log a prominent warning — e.g. “N SSO accounts exist for provider ‘keycloak interhop’ which is no longer configured” — and surface it in the admin panel.
- Explicit confirmation on SSO registration: before creating a brand-new account for a returning IdP identity, show “No existing account found for this identity — create a new one?” instead of registering silently. A user who knows they already have an account would stop right there.
- Documentation: state in the plugin README and in
sso.example.js that the provider identifier is persistent, keyed into the account database, and must never change once users have registered.
- Repair tool: an admin CLI/panel action “rename provider” that moves
sso_user/<old>/ (and associated sso_block entries) to the new identifier, turning this incident from apparent data loss into a documented maintenance operation.
Context
Hit this in production while migrating a Docker deployment to a new config repository: the new sso.js was rewritten from the example and the provider name changed from keycloak interhop to Keycloak. Same Keycloak realm and client, same nginx — yet every user came back to an empty drive. Recovery was easy once diagnosed (restore the exact old name and restart), but the failure mode is invisible and looks like total data loss.
Summary
The
namefield of each entry insso.js(list: [{ name: 'Keycloak', ... }]) looks like a cosmetic labelThe README only describes it as the text shown on the “Register with SSONAME” button. In reality it is also the storage key of the SSO user database: CryptPad core stores each SSO account's block seed under
(see
lib/storage/sso.js,userPathFromId()), and the plugin resolves providers with a strict string match (sso-utils.js,getProviderConfig:cfg.name === provider).If an admin ever changes
name(rebranding the login button, fixing capitalization, migrating config to a new file/repo…), every SSO user lookup misses, and CryptPad silently registers each returning user as a brand-new account with a freshly generated seed (SSOUtils.writeUser). Users log in successfully via their IdP as usual, but land in an empty drive, with no error and no hint of what happened. Their data is still on the server, unreachable, under the old provider directory.Steps to reproduce
sso.jswithname: 'keycloak interhop'.name: 'Keycloak'(sameurl,client_id, everything else identical).Expected: either the same account, or a clear error explaining the provider is unknown.
Actual: authentication succeeds and a new, empty account is silently created (
data/sso_user/Keycloak/...next to the orphaneddata/sso_user/keycloak interhop/...). The user's public key changes; drive, teams and contacts appear lost.Suggested fix
Decouple the stable identifier from the display label (backward compatible):
idfield per provider, and demotenameto a pure display label that admins can change freely.cfg.id || cfg.name: existing instances keep working unchanged (theirsso_user/<name>/directories remain valid), while new installations are encouraged to set a short stableid(e.g.keycloak) separate from the button text (e.g.Sign in with MyOrg).Complementary guard rails (each valuable on its own)
/checkupwarning: ifdata/sso_user/contains provider directories that don't match any configured provider, log a prominent warning — e.g. “N SSO accounts exist for provider ‘keycloak interhop’ which is no longer configured” — and surface it in the admin panel.sso.example.jsthat the provider identifier is persistent, keyed into the account database, and must never change once users have registered.sso_user/<old>/(and associatedsso_blockentries) to the new identifier, turning this incident from apparent data loss into a documented maintenance operation.Context
Hit this in production while migrating a Docker deployment to a new config repository: the new
sso.jswas rewritten from the example and the provider name changed fromkeycloak interhoptoKeycloak. Same Keycloak realm and client, same nginx — yet every user came back to an empty drive. Recovery was easy once diagnosed (restore the exact oldnameand restart), but the failure mode is invisible and looks like total data loss.