Skip to content

SSO provider name silently acts as a persistent primary key — renaming it orphans all SSO accounts #26

Description

@aparrot89

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

  1. Configure an OIDC provider in sso.js with name: 'keycloak interhop'.
  2. Register a user through SSO, put documents in the drive.
  3. Change the provider to name: 'Keycloak' (same url, client_id, everything else identical).
  4. 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)

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions