Skip to content

Commit 7cb615f

Browse files
committed
Phase 4b: Invite SSO enforcement + SSO routing
1 parent c81ef2d commit 7cb615f

18 files changed

+671
-62
lines changed

.claude/skills/setup-mocksaml/SKILL.md

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
name: setup-mocksaml
33
description: Set up MockSAML as a local SAML IdP for testing SSO flows (grant migration, invite enforcement). Run when setting up a fresh local env or after `supabase db reset`.
44
disable-model-invocation: true
5-
allowed-tools: Bash(*), Read
65
---
76

87
# Set Up MockSAML for Local SSO Testing
@@ -50,6 +49,18 @@ If `GOTRUE_SAML_ENABLED=true` is already set, skip to step 6.
5049
openssl genrsa 2048 > /tmp/saml_key.pem
5150
```
5251

52+
GoTrue requires PKCS#1 format. Check the header of the generated key:
53+
54+
```bash
55+
head -1 /tmp/saml_key.pem
56+
```
57+
58+
- `BEGIN RSA PRIVATE KEY`PKCS#1, good to go.
59+
- `BEGIN PRIVATE KEY`PKCS#8 (OpenSSL 3.x default). Convert it:
60+
```bash
61+
openssl rsa -traditional -in /tmp/saml_key.pem -out /tmp/saml_key.pem
62+
```
63+
5364
Strip to raw base64 (no PEM headers, no newlines):
5465

5566
```bash
@@ -67,10 +78,28 @@ Capture the current container's env vars, image, and network:
6778
<docker-prefix> docker inspect supabase_auth_flow --format '{{json .NetworkSettings.Networks}}'
6879
```
6980

70-
Stop and remove the old container, then recreate with all original env vars
71-
plus the SAML vars. **Important:** override `API_EXTERNAL_URL` to include the
72-
`/auth/v1` prefix — GoTrue uses this to generate the SAML ACS callback URL,
73-
and without the prefix Kong won't route the callback correctly.
81+
Build an env file for the new container. Using `--env-file` avoids shell
82+
parsing issues with values that contain template syntax (e.g.
83+
`GOTRUE_SMS_TEMPLATE=Your code is {{ .Code }}`).
84+
85+
```bash
86+
grep -v -E '^(PATH=|API_EXTERNAL_URL=)' /tmp/auth_env.txt > /tmp/auth_env_filtered.txt
87+
echo "GOTRUE_SAML_ENABLED=true" >> /tmp/auth_env_filtered.txt
88+
echo "GOTRUE_SAML_PRIVATE_KEY=$SAML_KEY_B64" >> /tmp/auth_env_filtered.txt
89+
echo "API_EXTERNAL_URL=http://127.0.0.1:5431/auth/v1" >> /tmp/auth_env_filtered.txt
90+
```
91+
92+
**Important:** the `API_EXTERNAL_URL` override includes the `/auth/v1` prefix —
93+
GoTrue uses this to generate the SAML ACS callback URL, and without the prefix
94+
Kong won't route the callback correctly.
95+
96+
If using Lima, copy the env file into the VM before running docker:
97+
98+
```bash
99+
limactl copy /tmp/auth_env_filtered.txt <vm>:/tmp/auth_env_filtered.txt
100+
```
101+
102+
Stop and remove the old container, then recreate:
74103

75104
```bash
76105
<docker-prefix> docker stop supabase_auth_flow && <docker-prefix> docker rm supabase_auth_flow
@@ -79,10 +108,7 @@ and without the prefix Kong won't route the callback correctly.
79108
--name supabase_auth_flow \
80109
--network <network-name> \
81110
--restart always \
82-
-e GOTRUE_SAML_ENABLED=true \
83-
-e GOTRUE_SAML_PRIVATE_KEY=$SAML_KEY_B64 \
84-
-e API_EXTERNAL_URL=http://127.0.0.1:5431/auth/v1 \
85-
<all original -e flags from /tmp/auth_env.txt, excluding PATH= and API_EXTERNAL_URL=> \
111+
--env-file /tmp/auth_env_filtered.txt \
86112
<image> auth
87113
```
88114

@@ -102,6 +128,15 @@ supabase status --output json
102128

103129
Extract `SERVICE_ROLE_KEY` from the output.
104130

131+
If `supabase status` fails (e.g. Docker runs inside a Lima VM), read the key
132+
from Kong's config instead — it's always accessible since Kong handles routing:
133+
134+
```bash
135+
<docker-prefix> docker exec supabase_kong_flow cat /home/kong/kong.yml
136+
```
137+
138+
Look for the `service_role` JWT in the authorization header rewriting rules.
139+
105140
### 7. Check if MockSAML is already registered
106141

107142
```bash
@@ -114,14 +149,20 @@ register a new one. If reusing, skip to step 9.
114149

115150
### 8. Register MockSAML as an SSO provider
116151

152+
Ask the user which email domain to associate with the SSO provider (default:
153+
`example.com`). This controls which email addresses are routed through SAML
154+
login. MockSAML's default test user is `jackson@example.com`, so `example.com`
155+
works out of the box — but the user may want a different domain to match their
156+
test data.
157+
117158
```bash
118159
curl -X POST 'http://127.0.0.1:5431/auth/v1/admin/sso/providers' \
119160
-H 'Authorization: Bearer <SERVICE_ROLE_KEY>' \
120161
-H 'Content-Type: application/json' \
121162
-d '{
122163
"type": "saml",
123164
"metadata_url": "https://mocksaml.com/api/saml/metadata",
124-
"domains": ["example.com"]
165+
"domains": ["<DOMAIN>"]
125166
}'
126167
```
127168

.sqlx/query-5c35576f65d7ef76f367b31b2238b49d8fdb0cca095fa3bb08380a62b238c1f3.json renamed to .sqlx/query-4f26f42a2519a323aefd30139d4124d620692f75f4bdad1bdaa607c42445d93f.json

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-62c24bfd77101313d6711554b2a275f088de07743cedd6b96d88c59a8e63256e.json

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-d66323852c5757f5102115ab5c73e3c180ff3b564cf55aea9dd06c07e0324421.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
do $$
2+
declare
3+
data_plane_one_id flowid := '111111111111';
4+
5+
alice_uid uuid := '11111111-1111-1111-1111-111111111111';
6+
bob_uid uuid := '22222222-2222-2222-2222-222222222222';
7+
carol_uid uuid := '33333333-3333-3333-3333-333333333333';
8+
9+
acme_provider_id uuid := 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
10+
other_provider_id uuid := 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
11+
12+
last_pub_id flowid := '000000000002';
13+
14+
begin
15+
16+
-- SSO providers
17+
insert into auth.sso_providers (id) values
18+
(acme_provider_id),
19+
(other_provider_id)
20+
;
21+
22+
-- Users
23+
insert into auth.users (id, email) values
24+
(alice_uid, 'alice@acme.co'),
25+
(bob_uid, 'bob@other.co'),
26+
(carol_uid, 'carol@example.com')
27+
;
28+
29+
-- Alice has an SSO identity matching acme's provider.
30+
-- GoTrue stores SSO identities with provider = 'sso:<provider_id>'.
31+
insert into auth.identities (user_id, provider, provider_id) values
32+
(alice_uid, 'sso:' || acme_provider_id::text, acme_provider_id::text)
33+
;
34+
35+
-- Bob has an SSO identity, but for a different provider.
36+
insert into auth.identities (user_id, provider, provider_id) values
37+
(bob_uid, 'sso:' || other_provider_id::text, other_provider_id::text)
38+
;
39+
40+
-- Carol has no SSO identity (social login only).
41+
42+
-- Tenants: acmeCo/ with SSO, openCo/ without SSO.
43+
insert into public.tenants (id, tenant, sso_provider_id) values
44+
(internal.id_generator(), 'acmeCo/', acme_provider_id),
45+
(internal.id_generator(), 'openCo/', null)
46+
;
47+
48+
-- Alice is admin on acmeCo/ and openCo/.
49+
insert into public.user_grants (user_id, object_role, capability) values
50+
(alice_uid, 'acmeCo/', 'admin'),
51+
(alice_uid, 'openCo/', 'admin')
52+
;
53+
54+
-- Give alice read on data planes so specs can resolve.
55+
insert into public.role_grants (subject_role, object_role, capability) values
56+
('acmeCo/', 'ops/dp/public/', 'read')
57+
;
58+
59+
end
60+
$$;

crates/control-plane-api/src/publications/quotas.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,7 @@ fn get_deltas(built: &build::Output) -> BTreeMap<&str, (i32, i32)> {
7474
}
7575

7676
fn tenant(name: &impl AsRef<str>) -> &str {
77-
let idx = name
78-
.as_ref()
79-
.find('/')
80-
.expect("catalog name must contain at least one /");
81-
name.as_ref().split_at(idx + 1).0
77+
models::tenant_prefix(name.as_ref())
8278
}
8379

8480
pub async fn check_resource_quotas(

0 commit comments

Comments
 (0)