|
| 1 | +-- An organization may run several identity providers, but sign-in routes by email domain, so |
| 2 | +-- each of its domains must name exactly one provider. The registration route checks this before |
| 3 | +-- writing, but that check is a plain read: two concurrent registrations with different provider |
| 4 | +-- ids can both see no sibling and both land. This index makes the invariant hold at the database. |
| 5 | +-- |
| 6 | +-- Keyed on the same expression the verify and resolve paths compare domains with, so a legacy |
| 7 | +-- leading `*.` or stray case cannot slip a second provider onto a domain already routed. |
| 8 | +-- |
| 9 | +-- Duplicates must be resolved first. Failing here, inside the transaction, avoids letting the |
| 10 | +-- CONCURRENT build fail afterwards and strand an INVALID index that IF NOT EXISTS would skip |
| 11 | +-- forever. Which row survives is a judgement call, so this reports the ids and stops. |
| 12 | +DO $$ |
| 13 | +DECLARE duplicate_provider_ids text; |
| 14 | +BEGIN |
| 15 | + SELECT string_agg(provider_id, ', ') |
| 16 | + INTO duplicate_provider_ids |
| 17 | + FROM "sso_provider" |
| 18 | + WHERE "organization_id" IS NOT NULL |
| 19 | + AND ("organization_id", lower(regexp_replace(btrim("domain"), '^\*\.', ''))) IN ( |
| 20 | + SELECT "organization_id", lower(regexp_replace(btrim("domain"), '^\*\.', '')) |
| 21 | + FROM "sso_provider" |
| 22 | + WHERE "organization_id" IS NOT NULL |
| 23 | + GROUP BY 1, 2 |
| 24 | + HAVING count(*) > 1 |
| 25 | + ); |
| 26 | + IF duplicate_provider_ids IS NOT NULL THEN |
| 27 | + RAISE EXCEPTION |
| 28 | + 'sso_provider has several providers on one organization domain: %. Keep one provider per (organization, domain) and re-run.', |
| 29 | + duplicate_provider_ids; |
| 30 | + END IF; |
| 31 | +END $$;--> statement-breakpoint |
| 32 | + |
| 33 | +COMMIT;--> statement-breakpoint |
| 34 | + |
| 35 | +-- `lock_timeout = 0` for the concurrent build, per packages/db/scripts/migrate.ts. |
| 36 | +-- CREATE INDEX CONCURRENTLY waits on every concurrent write in the database, not just |
| 37 | +-- this table, so the session's 5s DDL timeout would cancel it (55P03) and strand an |
| 38 | +-- INVALID index that the IF NOT EXISTS below would skip forever. |
| 39 | +SET lock_timeout = 0;--> statement-breakpoint |
| 40 | + |
| 41 | +-- Clear any INVALID index left by a previously cancelled build, so a replay |
| 42 | +-- rebuilds it instead of skipping it. |
| 43 | +DROP INDEX CONCURRENTLY IF EXISTS "sso_provider_org_domain_unique";--> statement-breakpoint |
| 44 | + |
| 45 | +CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS "sso_provider_org_domain_unique" ON "sso_provider" USING btree ("organization_id", lower(regexp_replace(btrim("domain"), '^\*\.', ''))) WHERE "sso_provider"."organization_id" is not null;--> statement-breakpoint |
| 46 | + |
| 47 | +SET lock_timeout = '5s'; |
0 commit comments