cryptpad / cryptpad/sso

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

Open
#26 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
27
Forks
7
PR merge metrics
No merged PRs in 30d

Description

## 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///.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//` 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//` (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.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with lib/storage/sso.js and its userPathFromId() logic, then trace provider lookup in sso-utils.js through getProviderConfig and SSOUtils.writeUser. Compare how the configured name is used for identity resolution and storage, and decide the compatible scope for separating a stable provider identifier from its display label. Done should prevent renamed providers from silently creating empty accounts while preserving existing account paths.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
authentication, backend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.