30 lines
1.5 KiB
SQL
30 lines
1.5 KiB
SQL
-- EntraID identity: key users on `oid` when the IdP emits it.
|
|||
|
|
--
|
||
|
|
-- EntraID's `sub` is a PAIRWISE identifier — derived from the token recipient,
|
||
|
|
-- so the Web UI app registration and the MCP app registration hand out
|
||
|
|
-- different `sub` values for the same person. Both `auth.ts` and
|
||
|
|
-- `lib/mcp/context.ts` upsert on (oidc_iss, oidc_sub), so on EntraID one human
|
||
|
|
-- resolves to two rows: they sign into the Web UI, connect an MCP client, and
|
||
|
|
-- land in an empty account with their memories nowhere to be seen. Nothing
|
||
|
|
-- errors, which is what makes it dangerous.
|
||
|
|
--
|
||
|
|
-- `oid` is the user's directory object id, which Microsoft documents as
|
||
|
|
-- constant for a user across every application in a tenant. Recording it gives
|
||
|
|
-- us a key that holds across both surfaces.
|
||
|
|
--
|
||
|
|
-- Nullable on purpose: Authentik, Keycloak and Okta emit no `oid`, and there
|
||
|
|
-- `sub` is already application-independent. Those deployments keep using
|
||
|
|
-- (oidc_iss, oidc_sub) and are untouched by this migration.
|
||
|
|
|
||
|
|
ALTER TABLE "users" ADD COLUMN "oidc_oid" text;
|
||
|
|
|
||
|
|
-- Partial index. Every non-EntraID row holds NULL here; a plain unique index
|
||
|
|
-- would treat those as colliding and permit exactly one such user.
|
||
|
|
CREATE UNIQUE INDEX "users_iss_oid_uq"
|
||
|
|
ON "users" ("oidc_iss", "oidc_oid")
|
||
|
|
WHERE "oidc_oid" IS NOT NULL;
|
||
|
|
|
||
|
|
-- No backfill. `oid` is only knowable from a token, so existing rows adopt
|
||
|
|
-- theirs on the owner's next sign-in (see `adoptLegacyRow` in
|
||
|
|
-- lib/auth/identity.ts). Backfilling would mean guessing.
|