Skip to content

[DRAFT] feat(dashboard-api): integrate ory bootstrap and user profile provider#2809

Closed
ben-fornefeld wants to merge 25 commits into
mainfrom
full-stack
Closed

[DRAFT] feat(dashboard-api): integrate ory bootstrap and user profile provider#2809
ben-fornefeld wants to merge 25 commits into
mainfrom
full-stack

Conversation

@ben-fornefeld
Copy link
Copy Markdown
Member

Summary

  • Add an admin-token POST /admin/users/bootstrap route that accepts OIDC user id/email/name and bootstraps the user without a user id path param.
  • Upsert public.users and public.user_identities for OIDC users before creating/reusing the default team.
  • Move Supabase bootstrap profile name resolution behind userprofile.Provider while keeping the existing /admin/users/{userId}/bootstrap route unchanged.

Test plan

  • go test ./packages/dashboard-api/internal/userprofile ./packages/dashboard-api ./packages/dashboard-api/internal/handlers ./packages/dashboard-api/internal/cfg

@cla-bot cla-bot Bot added the cla-signed label May 23, 2026
@cursor
Copy link
Copy Markdown

cursor Bot commented May 23, 2026

PR Summary

High Risk
Touches admin bootstrap, OIDC identity storage, and issuer alignment; misconfiguration can strand users or allow wrong-issuer identity rows. Ory mode depends on external API and primary DB reads for identity mapping.

Overview
This PR lets dashboard-api onboard generic OIDC users and resolve display profiles from Supabase, Ory, or a Supabase-then-Ory fallback, instead of assuming Supabase-only bootstrap and profile reads.

A new admin POST /admin/users/bootstrap accepts oidc_issuer, oidc_user_id, oidc_user_email, and optional name, validates the issuer against configured JWT/Ory settings, upserts public.users and public.user_identities (with concurrency-safe canonical user IDs), then creates or reuses the default team. The legacy /admin/users/{userId}/bootstrap path stays for Supabase-backed flows but uses the shared profile provider for naming. Team member responses now include name, profile picture, and linked providers. Deploy wiring adds USER_PROFILE_PROVIDER, Ory SDK/issuer env vars, and a GCP Secret Manager slot for the Ory project API token. Shared auth middleware now sets 401 when a security scheme is skipped so failed auth is not reported as 400.

Reviewed by Cursor Bugbot for commit ad40cb0. Bugbot is set up for automated code reviews on this repo. Configure here.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 23, 2026

❌ 8 Tests Failed:

Tests completed Failed Passed Skipped
2676 8 2668 7
View the full list of 8 ❄️ flaky test(s)
github.com/e2b-dev/infra/tests/integration/internal/tests/api/sandboxes::TestSandboxListPaginationRunningLargerLimit

Flake rate in main: 43.17% (Passed 674 times, Failed 512 times)

Stack Traces | 33.7s run time
=== RUN   TestSandboxListPaginationRunningLargerLimit
    sandbox_list_test.go:327: Created sandbox 1/12: id265uvue8z4xe6thecvs
    sandbox_list_test.go:327: Created sandbox 2/12: i3vzsupfquzehvpr2qh2h
    sandbox_list_test.go:327: Created sandbox 3/12: izdyrgu132a8zsa9crwzd
    sandbox_list_test.go:327: Created sandbox 4/12: i80nef3csfeact8iv07kw
    sandbox_list_test.go:327: Created sandbox 5/12: ir2xk4kpaku6fln41dkbu
    sandbox_list_test.go:327: Created sandbox 6/12: il5qik1hldqylnksh2u0t
    sandbox_list_test.go:327: Created sandbox 7/12: iaklfp65cewv4dn00ufip
    sandbox_list_test.go:327: Created sandbox 8/12: ih5uj0wp9fdzmhileqxep
    sandbox_list_test.go:327: Created sandbox 9/12: ik9f4p5rjspwukwm0doul
    sandbox_list_test.go:327: Created sandbox 10/12: iatbsxxiss64yxzsm73w9
    sandbox_list_test.go:327: Created sandbox 11/12: iuv0sulrpd08ir3z3z8zf
    sandbox_list_test.go:327: Created sandbox 12/12: ipqs3uykq1lvqw8gupmfb
--- FAIL: TestSandboxListPaginationRunningLargerLimit (33.73s)
github.com/e2b-dev/infra/tests/integration/internal/tests/api/sandboxes::TestSandboxListPaginationRunningLargerLimit/check_all_sandboxes_list

Flake rate in main: 43.53% (Passed 663 times, Failed 511 times)

Stack Traces | 0.02s run time
=== RUN   TestSandboxListPaginationRunningLargerLimit/check_all_sandboxes_list
=== PAUSE TestSandboxListPaginationRunningLargerLimit/check_all_sandboxes_list
=== CONT  TestSandboxListPaginationRunningLargerLimit/check_all_sandboxes_list
    sandbox_list_test.go:339: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:339
        	Error:      	"[{0xc4cf074ed0 6532622b 2 1982 2026-05-28 21:06:54.812490024 +0000 UTC 0.6.1 512 0xc4cf182070 ipqs3uykq1lvqw8gupmfb 2026-05-28 21:06:24.812490024 +0000 UTC running 2j6ly824owf4awgai1xo 0xc4cf612228} {0xc4cf074f10 6532622b 2 1982 2026-05-28 21:06:53.576160977 +0000 UTC 0.6.1 512 0xc4cf182078 iuv0sulrpd08ir3z3z8zf 2026-05-28 21:06:23.576160977 +0000 UTC running 2j6ly824owf4awgai1xo 0xc4cf612258} {0xc4cf074f50 6532622b 2 1982 2026-05-28 21:06:47.877635028 +0000 UTC 0.6.1 512 0xc4cf182080 iatbsxxiss64yxzsm73w9 2026-05-28 21:06:17.877635028 +0000 UTC running 2j6ly824owf4awgai1xo 0xc4cf612288} {0xc4cf074f90 6532622b 2 1982 2026-05-28 21:06:46.502192451 +0000 UTC 0.6.1 512 0xc4cf182088 ik9f4p5rjspwukwm0doul 2026-05-28 21:06:16.502192451 +0000 UTC running 2j6ly824owf4awgai1xo 0xc4cf6122b8} {0xc4cf074fd0 6532622b 2 1982 2026-05-28 21:06:42.350741156 +0000 UTC 0.6.1 512 0xc4cf182090 ih5uj0wp9fdzmhileqxep 2026-05-28 21:06:12.350741156 +0000 UTC running 2j6ly824owf4awgai1xo 0xc4cf6122e8} {0xc4cf075010 6532622b 2 1982 2026-05-28 21:06:41.633842622 +0000 UTC 0.6.1 512 0xc4cf182098 iaklfp65cewv4dn00ufip 2026-05-28 21:06:11.633842622 +0000 UTC running 2j6ly824owf4awgai1xo 0xc4cf612318} {0xc4cf075050 6532622b 2 1982 2026-05-28 21:06:41.122026871 +0000 UTC 0.6.1 512 0xc4cf1820a0 il5qik1hldqylnksh2u0t 2026-05-28 21:06:11.122026871 +0000 UTC running 2j6ly824owf4awgai1xo 0xc4cf612348} {0xc4cf075090 6532622b 2 1982 2026-05-28 21:06:39.076396129 +0000 UTC 0.6.1 512 0xc4cf1820a8 ir2xk4kpaku6fln41dkbu 2026-05-28 21:06:09.076396129 +0000 UTC running 2j6ly824owf4awgai1xo 0xc4cf612378} {0xc4cf0750d0 6532622b 2 1982 2026-05-28 21:06:37.782203736 +0000 UTC 0.6.1 512 0xc4cf1820b0 i80nef3csfeact8iv07kw 2026-05-28 21:06:07.782203736 +0000 UTC running 2j6ly824owf4awgai1xo 0xc4cf6123a8} {0xc4cf075110 6532622b 2 1982 2026-05-28 21:06:27.651511974 +0000 UTC 0.6.1 512 0xc4cf1820b8 izdyrgu132a8zsa9crwzd 2026-05-28 21:05:57.651511974 +0000 UTC running 2j6ly824owf4awgai1xo 0xc4cf6123d8}]" should have 12 item(s), but has 10
        	Test:       	TestSandboxListPaginationRunningLargerLimit/check_all_sandboxes_list
--- FAIL: TestSandboxListPaginationRunningLargerLimit/check_all_sandboxes_list (0.02s)
github.com/e2b-dev/infra/tests/integration/internal/tests/api/sandboxes::TestSandboxListPaginationRunningLargerLimit/check_paginated_list

Flake rate in main: 43.53% (Passed 663 times, Failed 511 times)

Stack Traces | 0.08s run time
=== RUN   TestSandboxListPaginationRunningLargerLimit/check_paginated_list
=== PAUSE TestSandboxListPaginationRunningLargerLimit/check_paginated_list
=== CONT  TestSandboxListPaginationRunningLargerLimit/check_paginated_list
    sandbox_list_test.go:368: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:368
        	Error:      	Not equal: 
        	            	expected: 12
        	            	actual  : 10
        	Test:       	TestSandboxListPaginationRunningLargerLimit/check_paginated_list
    sandbox_list_test.go:368: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:368
        	Error:      	Not equal: 
        	            	expected: 12
        	            	actual  : 10
        	Test:       	TestSandboxListPaginationRunningLargerLimit/check_paginated_list
    sandbox_list_test.go:368: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:368
        	Error:      	Not equal: 
        	            	expected: 12
        	            	actual  : 10
        	Test:       	TestSandboxListPaginationRunningLargerLimit/check_paginated_list
    sandbox_list_test.go:368: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:368
        	Error:      	Not equal: 
        	            	expected: 12
        	            	actual  : 10
        	Test:       	TestSandboxListPaginationRunningLargerLimit/check_paginated_list
    sandbox_list_test.go:368: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:368
        	Error:      	Not equal: 
        	            	expected: 12
        	            	actual  : 10
        	Test:       	TestSandboxListPaginationRunningLargerLimit/check_paginated_list
    sandbox_list_test.go:375: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:375
        	Error:      	Should NOT be empty, but was 
        	Test:       	TestSandboxListPaginationRunningLargerLimit/check_paginated_list
    sandbox_list_test.go:363: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:363
        	Error:      	Not equal: 
        	            	expected: "i3vzsupfquzehvpr2qh2h"
        	            	actual  : "ipqs3uykq1lvqw8gupmfb"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -1 +1 @@
        	            	-i3vzsupfquzehvpr2qh2h
        	            	+ipqs3uykq1lvqw8gupmfb
        	Test:       	TestSandboxListPaginationRunningLargerLimit/check_paginated_list
        	Messages:   	page starting at 10 should start with sandbox i3vzsupfquzehvpr2qh2h, token 
    sandbox_list_test.go:368: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:368
        	Error:      	Not equal: 
        	            	expected: 12
        	            	actual  : 10
        	Test:       	TestSandboxListPaginationRunningLargerLimit/check_paginated_list
    sandbox_list_test.go:373: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:373
        	Error:      	Should be empty, but was MjAyNi0wNS0yOFQyMTowNjoyMy41NzYxNjA5NzdaX19pdXYwc3VscnBkMDhpcjN6M3o4emY=
        	Test:       	TestSandboxListPaginationRunningLargerLimit/check_paginated_list
--- FAIL: TestSandboxListPaginationRunningLargerLimit/check_paginated_list (0.08s)
github.com/e2b-dev/infra/tests/integration/internal/tests/orchestrator::TestSandboxMemoryIntegrity

Flake rate in main: 58.22% (Passed 668 times, Failed 931 times)

Stack Traces | 70.4s run time
=== RUN   TestSandboxMemoryIntegrity
=== PAUSE TestSandboxMemoryIntegrity
=== CONT  TestSandboxMemoryIntegrity
    sandbox_memory_integrity_test.go:27: Build completed successfully
--- FAIL: TestSandboxMemoryIntegrity (70.38s)
github.com/e2b-dev/infra/tests/integration/internal/tests/orchestrator::TestSandboxMemoryIntegrity/tmpfs_hash

Flake rate in main: 58.35% (Passed 658 times, Failed 922 times)

Stack Traces | 105s run time
=== RUN   TestSandboxMemoryIntegrity/tmpfs_hash
=== PAUSE TestSandboxMemoryIntegrity/tmpfs_hash
=== CONT  TestSandboxMemoryIntegrity/tmpfs_hash
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{start:{pid:1270}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stdout:"Total memory: 985 MB\nUsed memory before tmpfs mount: 192 MB\nFree memory before tmpfs mount: 792 MB\nMemory to use in integrity test (80% of free, min 64MB): 633 MB\n"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"633+0 records in\n633+0 records out\n663748608 bytes (664 MB, 633 MiB) copied, 3.31147 s, 200 MB/s\n"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"\tCommand being timed: \"dd if=/dev/urandom of=/mnt/testfile bs=1M count=633\"\n\tUser time (seconds): 0.00\n\tSystem time (seconds): 3.27\n\tPercent of CPU this job got: 98%\n\tElapsed (wall clock) time (h:mm:ss or m:ss): 0:03.31\n\tAverage shared text size (kbytes): 0\n\tAverage unshared data size (kbytes): 0\n\tAverage stack size (kbytes): 0\n\tAverage total size (kbytes): 0\n\tMaximum resident set size (kbytes): 2652\n\tAverage resident set size (kbytes): 0\n\tMajor (requiring I/O) page faults: 3\n\tMinor (reclaiming a frame) page faults: 343\n\tVoluntary context switches: 4\n\tInvoluntary context switches: 19\n\tSwaps: 0\n\tFile system inputs: 176\n\tFile system outputs: 0\n\tSocket messages sent: 0\n\tSocket messages received: 0\n\tSignals delivered: 0\n\tPage size (bytes): 4096\n\tExit status: 0\n"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stdout:"Used memory after tmpfs mount and file fill: 829 MB\n"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{end:{exited:true  status:"exit status 0"}}
    sandbox_memory_integrity_test.go:70: Command [bash] completed successfully in sandbox ij5lbum656kp9wji0mcsh
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
    sandbox_memory_integrity_test.go:80: Command [bash] output: event:{start:{pid:1287}}
    sandbox_memory_integrity_test.go:80: Command [bash] output: event:{data:{stdout:"0b11efbe516c63678cf5fa7a4d78be68913758c69485b8d837af9690dcc6effc\n"}}
    sandbox_memory_integrity_test.go:80: Command [bash] output: event:{end:{exited:true  status:"exit status 0"}}
    sandbox_memory_integrity_test.go:80: Command [bash] completed successfully in sandbox ij5lbum656kp9wji0mcsh
Executing command bash in sandbox inm3ny38vyfgewxwmicz6 (user: root)
    sandbox_memory_integrity_test.go:80: Command [bash] output: event:{start:{pid:1290}}
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
Executing command bash in sandbox ij5lbum656kp9wji0mcsh (user: root)
    sandbox_memory_integrity_test.go:110: 
        	Error Trace:	.../tests/orchestrator/sandbox_memory_integrity_test.go:81
        	            				.../hostedtoolcache/go/1.26.3.../src/runtime/asm_amd64.s:1771
        	Error:      	Received unexpected error:
        	            	failed to execute command bash in sandbox ij5lbum656kp9wji0mcsh: unavailable: HTTP status 502 Bad Gateway
    sandbox_memory_integrity_test.go:110: 
        	Error Trace:	.../tests/orchestrator/sandbox_memory_integrity_test.go:78
        	            				.../tests/orchestrator/sandbox_memory_integrity_test.go:110
        	Error:      	Condition never satisfied
        	Test:       	TestSandboxMemoryIntegrity/tmpfs_hash
--- FAIL: TestSandboxMemoryIntegrity/tmpfs_hash (104.58s)
github.com/e2b-dev/infra/tests/integration/internal/tests/orchestrator::TestSandboxObjectNotFound

Flake rate in main: 43.14% (Passed 663 times, Failed 503 times)

Stack Traces | 50.4s run time
=== RUN   TestSandboxObjectNotFound
=== PAUSE TestSandboxObjectNotFound
=== CONT  TestSandboxObjectNotFound
    sandbox_object_not_found_test.go:59: sandbox creation failed due to resource exhaustion, retrying
    sandbox_object_not_found_test.go:59: sandbox creation failed due to resource exhaustion, retrying
    sandbox_object_not_found_test.go:59: sandbox creation failed due to resource exhaustion, retrying
Executing command bash in sandbox ia91hh7od8829bbpqpgm9 (user: root)
    sandbox_object_not_found_test.go:59: sandbox creation failed due to resource exhaustion, retrying
Executing command bash in sandbox iciqspo7zt9fkdal3hqsv (user: root)
    sandbox_object_not_found_test.go:59: sandbox creation failed due to resource exhaustion, retrying
    sandbox_object_not_found_test.go:59: sandbox creation failed due to resource exhaustion, retrying
    sandbox_object_not_found_test.go:59: sandbox creation failed due to resource exhaustion, retrying
Executing command bash in sandbox iciqspo7zt9fkdal3hqsv (user: root)
    sandbox_object_not_found_test.go:59: sandbox creation failed due to resource exhaustion, retrying
    sandbox_object_not_found_test.go:59: sandbox creation failed due to resource exhaustion, retrying
    sandbox_object_not_found_test.go:59: sandbox creation failed due to resource exhaustion, retrying
    sandbox_object_not_found_test.go:70: failed to create sandbox after 10 retries
--- FAIL: TestSandboxObjectNotFound (50.38s)
github.com/e2b-dev/infra/tests/integration/internal/tests/proxies::TestEnvdAccessTokenAutoResumeViaProxy

Flake rate in main: 43.57% (Passed 663 times, Failed 512 times)

Stack Traces | 10.9s run time
=== RUN   TestEnvdAccessTokenAutoResumeViaProxy
=== PAUSE TestEnvdAccessTokenAutoResumeViaProxy
=== CONT  TestEnvdAccessTokenAutoResumeViaProxy
    traffic_access_token_test.go:357: 
        	Error Trace:	.../tests/proxies/traffic_access_token_test.go:357
        	Error:      	Received unexpected error:
        	            	Get "http://localhost:3002/health": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
        	Test:       	TestEnvdAccessTokenAutoResumeViaProxy
--- FAIL: TestEnvdAccessTokenAutoResumeViaProxy (10.90s)
github.com/e2b-dev/infra/tests/integration/internal/tests/proxies::TestSandboxAutoResumeViaProxy

Flake rate in main: 44.32% (Passed 662 times, Failed 527 times)

Stack Traces | 12.9s run time
=== RUN   TestSandboxAutoResumeViaProxy
=== PAUSE TestSandboxAutoResumeViaProxy
=== CONT  TestSandboxAutoResumeViaProxy
    auto_resume_test.go:116: 
        	Error Trace:	.../tests/proxies/auto_resume_test.go:116
        	Error:      	Received unexpected error:
        	            	Get "http://localhost:3002": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
        	Test:       	TestSandboxAutoResumeViaProxy
--- FAIL: TestSandboxAutoResumeViaProxy (12.86s)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The resolveOIDCUserID function contains a security vulnerability by attempting to use the OIDC subject directly as an internal UserID if it is a valid UUID. This allows an external OIDC identity to potentially collide with or target existing internal accounts, leading to unauthorized account access. OIDC subjects must always be treated as external identifiers and should be deterministically mapped to the internal namespace using the hashing logic, ensuring isolation from other user identification schemes.

Comment thread packages/dashboard-api/internal/handlers/utils_team_provisioning.go Outdated
Comment thread packages/dashboard-api/internal/handlers/utils_team_provisioning.go
Comment thread packages/dashboard-api/internal/handlers/utils_team_provisioning.go
Comment thread packages/dashboard-api/internal/handlers/admin_users_bootstrap.go
Comment thread packages/dashboard-api/internal/userprofile/supabase.go
@ben-fornefeld ben-fornefeld marked this pull request as ready for review May 25, 2026 03:21
@e2b-atlantis
Copy link
Copy Markdown

e2b-atlantis Bot commented May 25, 2026

Error: This repo is not allowlisted for Atlantis.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 145d6bc829

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/dashboard-api/internal/handlers/utils_team_provisioning.go Outdated
Comment thread packages/dashboard-api/internal/handlers/admin_users_bootstrap.go
- Change UpsertPublicIdentity to return the canonical user_id and stop
  overwriting it on conflict so concurrent bootstraps converge on a
  single (issuer, subject) -> user_id mapping.
- Move identity resolution inside the bootstrap transaction; clean up
  the orphan candidate public.users row when a concurrent request wins
  the race for a fresh identity.
- Reject blank oidc_user_id / oidc_user_email in PostAdminUsersBootstrap
  so empty strings can't share a single placeholder identity.
- Export userprofile.FirstNonEmpty and drop the duplicate helper in
  handlers.
Use errors.New for the static empty-input error instead of fmt.Errorf.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 849be2216f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread spec/openapi-dashboard.yml Outdated
Document that PostAdminUsersUserIdBootstrap is the legacy Supabase-only
path and PostAdminUsersBootstrap is the entry point for generic OIDC
provider deployments.
The OapiRequestValidator middleware enforces the OpenAPI required list,
so the previous schema rejected bootstrap calls that omitted
oidc_user_name even though the handler explicitly supports a missing or
blank name by defaulting to "Default Team".
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 04c74aa6bd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/dashboard-api/internal/handlers/utils_team_provisioning.go
Comment thread packages/dashboard-api/internal/handlers/utils_team_provisioning.go Outdated
Lets s.userProfiles resolve members and email lookups against an Ory
identity API in addition to Supabase auth.users. USER_PROFILE_PROVIDER
selects supabase, ory, or supabase-ory-fallback (dual provider that
prefers supabase and falls back to ory for ids missing locally).

The ory provider maps internal user UUIDs to Ory subject ids via two
new sqlc queries against public.user_identities (by user_ids and by
subjects), scoped to the configured oidc_iss so a future multi-issuer
deployment cannot cross-resolve.
The bootstrap endpoint previously assumed exactly one configured OIDC
issuer and silently planted the resulting identity under that single
iss. The caller now passes oidc_issuer in the request body and the
handler checks it against the configured AuthProvider.JWT list,
rejecting unknown issuers with 400. This both unblocks multi-issuer
deployments and prevents an admin-token holder from planting an
identity under an arbitrary iss.
…ovider

The ory userprofile provider previously borrowed its issuer from
config.AuthProvider.JWT[0].Issuer.URL and required exactly one
configured jwt issuer. That conflated two unrelated concerns: which
issuers we accept incoming JWTs from (a verification concern) and
which oidc_iss value we use to scope public.user_identities lookups
(a property of the ory tenant deployment).

Introduce ORY_ISSUER_URL as an explicit config field. Profile
resolution now works regardless of how many - or zero - jwt issuers
are configured, and is compatible with the multi-issuer bootstrap
allow-list added in 2a4264e.
For the common deployments (default Ory Network and single-endpoint
self-hosted setups) the issuer is the same string as the admin SDK
URL. Default the issuer to the SDK URL so those callers only need to
configure one knob. Custom-domain Ory Network and split Hydra/Kratos
deployments can still override ORY_ISSUER_URL explicitly.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f16a246d07

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/dashboard-api/internal/cfg/model.go Outdated
The Ory profile provider filters public.user_identities by oidc_iss,
which gets stored at bootstrap as the auth-provider issuer URL. The
SDK admin URL (e.g. https://tenant.projects.oryapis.com) is not the
OIDC iss claim, so defaulting to it silently strands every user once
the deployment uses a custom issuer URL.

- Drop the silent ORY_ISSUER_URL = ORY_SDK_URL fallback.
- When AUTH_PROVIDER_CONFIG has exactly one JWT issuer, default
  ORY_ISSUER_URL from it.
- When AUTH_PROVIDER_CONFIG has any JWT entries, reject an
  ORY_ISSUER_URL that doesn't match any of them so the mismatch
  surfaces at startup instead of as missing profile lookups.
Comment thread packages/dashboard-api/internal/handlers/utils_team_provisioning.go
- Capture and drain *http.Response from Ory ListIdentitiesExecute so
  the connection is released (bodyclose).
- Realign USER_PROFILE_PROVIDER env tag (tagalign).
- Update local-dev seed to the new UpsertPublicIdentity :one signature
  after merging main.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a4f6eaf64

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/dashboard-api/internal/handlers/utils_team_provisioning.go
Plumbs the four new dashboard-api configuration env vars through the
.env.gcp -> Makefile -> root terraform -> nomad module -> job module
chain so that make plan-only-jobs/dashboard-api picks them up.

- Add USER_PROFILE_PROVIDER, ORY_SDK_URL, ORY_ISSUER_URL as plain
  terraform variables.
- Add ORY_PROJECT_API_TOKEN as a Google Secret Manager secret with a
  placeholder value + ignore_changes so operators set it out-of-band.
- Pass them into module.dashboard_api and inject into the nomad job
  env only when non-empty so the binary keeps its supabase default.
Comment thread packages/dashboard-api/internal/handlers/utils_team_provisioning.go
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 912eb1a40a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/dashboard-api/internal/handlers/utils_team_provisioning.go
Comment on lines +54 to +56
secondary, err := p.secondary.FindProfilesByEmail(ctx, email)
if err != nil {
return nil, err
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep fallback email lookup available on secondary outage

In supabase-ory-fallback mode, FindProfilesByEmail always calls the secondary provider and propagates its error, so a transient Ory failure turns profile/email resolution endpoints into 500s even when the primary (Supabase) already returned results. That breaks the expected availability behavior of a fallback mode for email-based lookups.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine, this is temporary

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c41c17c. Configure here.

Comment thread packages/dashboard-api/main.go Outdated
cfg permits Ory user-profile mode with ORY_ISSUER_URL set and no
AUTH_PROVIDER_CONFIG.jwt entries, but requireConfiguredOIDCIssuer only
consulted JWT issuers, so the configured issuer was rejected with 400 at
bootstrap time.

Additionally, in Ory user-profile mode the resolver only queries
public.user_identities for OidcIss = OryIssuerURL, so identities created
under any other configured JWT issuer would be stranded. When Ory mode
is on, restrict bootstrap to ORY_ISSUER_URL only; otherwise allow any
configured JWT issuer plus ORY_ISSUER_URL when set.
The Ory profile provider's identity resolver reads public.user_identities
to translate between user_id and OIDC subject. Routing that through
authDB.Read uses the read replica when AUTH_DB_READ_REPLICA_CONNECTION_STRING
is set, but the OIDC bootstrap path writes those rows on the primary inside
a transaction, so a profile lookup immediately after bootstrap can race
replication lag and return no profile. Pin identity resolution to the
primary; the wider profile/team queries keep using the replica.
The Ory project standardizes on flat traits regardless of upstream OIDC
provider: name, email, profile_picture_url. Extend Profile with
ProfilePictureURL and read it from traits.profile_picture_url. Drop the
old multi-shape name fallback chain (nested first/last, given/family,
full_name) now that the trait schema is committed.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

base http.RoundTripper
}

func (t *oryBearerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Agentic Security Review
Severity: HIGH
oryBearerTransport.RoundTrip injects the PAT into every outbound request at transport level, so if the Ory endpoint responds with a redirect to a different host the follow-up request can carry the same Authorization bearer token to that new origin. This turns redirect handling into a credential exfiltration path for the admin PAT rather than constraining it to the configured Ory host.

Impact: An attacker who can influence redirects on this path can capture the Ory admin token and use it to access or mutate identity data through privileged Ory APIs.

Commits the v1 trait schema applied to the Ory project so the contract
between profileFromOryIdentity and Ory is visible in-repo. Adds
profile_picture_url alongside email and name to match what the resolver
reads. Pointer comment added on profileFromOryIdentity so the consuming
code links to the schema.
Align with the OIDC standard claim name (picture). The internal Go
field stays ProfilePictureURL for descriptiveness; only the Ory trait
key, fixture schema, and test cases change.
Move user-profile enrichment out of the dashboard and onto dashboard-api
so list-members is a single backend call instead of a per-member Ory
admin fanout.

- Extend userprofile.Profile with Providers []string.
- Ory provider now requests credentials via include_credential=oidc and
  reads upstream providers from credentials.oidc.config.providers[]
  (with a fallback to identifier prefixes).
- Supabase provider reads providers from raw_app_meta_data for parity.
- TeamMember API schema gains name, profilePictureUrl, providers.
- Handler projects the new profile fields through.
# Conflicts:
#	packages/dashboard-api/internal/api/api.gen.go
#	spec/openapi-dashboard.yml
The shared authenticator logged "authorization header is missing" at WARN
on every per-scheme miss, which fires on each request in Ory mode because
the Supabase schemes are evaluated first and never match. Demote to a span
event so the trace still records which schemes were skipped, but no log
line is emitted. The 401 status stamp is preserved with an inline note
explaining why ErrorHandler depends on it.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5304e8c845

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/dashboard-api/internal/api/api.gen.go
POST /admin/users/bootstrap (static) and POST /admin/users/:userId/bootstrap
(parameterized) coexist at the same path level. gin's router currently handles
this correctly, but a future upgrade or router fork swap could regress it. The
test asserts no-panic at registration and that both URLs resolve to their
intended handler.
Drop forbidden gin.SetMode call (CI sets GIN_MODE=test), switch to
httptest.NewRequestWithContext, and add t.Parallel() at both levels.
@ben-fornefeld
Copy link
Copy Markdown
Member Author

Split into #2840 (Ory user profile provider + auth middleware) and #2841 (OIDC admin bootstrap). The full-stack branch is preserved at refs/backup/full-stack-pre-split locally.

@ben-fornefeld ben-fornefeld changed the title feat(dashboard-api): add OIDC user bootstrap endpoint [DRAFT] feat(dashboard-api): integrate ory bootstrap and user profile provider May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants