@@ -12,6 +12,95 @@ const MGMT_API_BASE = MGMT_API_BASE_RAW.match(/^https?:\/\//)
1212 ? MGMT_API_BASE_RAW
1313 : `https://${ MGMT_API_BASE_RAW } `
1414
15+ // Returns true if the projectRef passed is a non-branch project, false otherwise
16+ async function canAccessProject ( projectRef : string , accessToken : string ) : Promise < boolean > {
17+ const url = `${ MGMT_API_BASE } /v1/projects/${ projectRef } `
18+ const response = await fetch ( url , {
19+ method : 'GET' ,
20+ headers : {
21+ Authorization : `Bearer ${ accessToken } ` ,
22+ 'Content-Type' : 'application/json' ,
23+ } ,
24+ } )
25+ return response . ok
26+ }
27+
28+ // Returns all projects refs
29+ async function getAllProjects ( accessToken : string ) : Promise < string [ ] > {
30+ const url = `${ MGMT_API_BASE } /v1/projects`
31+ const response = await fetch ( url , {
32+ method : 'GET' ,
33+ headers : {
34+ Authorization : `Bearer ${ accessToken } ` ,
35+ 'Content-Type' : 'application/json' ,
36+ } ,
37+ } )
38+ if ( response . ok ) {
39+ const projects = ( await response . json ( ) ) as Array < { ref ?: string } >
40+ return Array . isArray ( projects )
41+ ? projects
42+ . map ( ( project ) => project . ref )
43+ . filter ( ( projectRef ) : projectRef is string => Boolean ( projectRef ) )
44+ : [ ]
45+ } else {
46+ return [ ]
47+ }
48+ }
49+
50+ // Returns all branch project refs of a give projectRef
51+ async function getAllBranches ( projectRef : string , accessToken : string ) : Promise < string [ ] > {
52+ const url = `${ MGMT_API_BASE } /v1/projects/${ projectRef } /branches`
53+ const response = await fetch ( url , {
54+ method : 'GET' ,
55+ headers : {
56+ Authorization : `Bearer ${ accessToken } ` ,
57+ 'Content-Type' : 'application/json' ,
58+ } ,
59+ } )
60+ if ( response . ok ) {
61+ const branches = ( await response . json ( ) ) as Array < { project_ref ?: string } >
62+ return Array . isArray ( branches )
63+ ? branches
64+ . map ( ( branch ) => branch . project_ref )
65+ . filter ( ( projectRef ) : projectRef is string => Boolean ( projectRef ) )
66+ : [ ]
67+ } else {
68+ return [ ]
69+ }
70+ }
71+
72+ // Authenticates with Supabase management API. Returns null if the accessToken has
73+ // access to the projectRef project, other returns an error string
74+ async function authenticateWithSupabase (
75+ projectRef : string ,
76+ accessToken : string | null
77+ ) : Promise < string | null > {
78+ if ( ! accessToken ) {
79+ return 'Unauthorized: Invalid credentials'
80+ }
81+
82+ // First we check if the token can return the project directly
83+ // If it does then the token is valid for this project and
84+ // we authenticate the request
85+ const tokenCanAccesProject = await canAccessProject ( projectRef , accessToken )
86+ if ( tokenCanAccesProject ) {
87+ return null
88+ }
89+
90+ // If the token does not return a project then projectRef could be a branch
91+ // project, in which case we enumerate branch projects of all projects
92+ const allProjectRefs = await getAllProjects ( accessToken )
93+ for ( const ref of allProjectRefs ) {
94+ const branches = await getAllBranches ( ref , accessToken )
95+ if ( branches . includes ( projectRef ) ) {
96+ return null
97+ }
98+ }
99+
100+ // It's not even a branch project
101+ return 'Unauthorized: Invalid credentials'
102+ }
103+
15104// Helper to delete edge function via Management API
16105async function deleteEdgeFunction (
17106 projectRef : string ,
@@ -74,6 +163,11 @@ Deno.serve(async (req) => {
74163
75164 const accessToken = authHeader . substring ( 7 ) // Remove 'Bearer '
76165
166+ const supabaseAuthError = await authenticateWithSupabase ( projectRef , accessToken )
167+ if ( supabaseAuthError ) {
168+ return new Response ( 'Unauthorized' , { status : 401 } )
169+ }
170+
77171 // Handle GET requests for status
78172 if ( req . method === 'GET' ) {
79173 const dbUrl = Deno . env . get ( 'SUPABASE_DB_URL' )
0 commit comments