Skip to content

Commit c8cb7da

Browse files
committed
move to a separate function
1 parent 58b4848 commit c8cb7da

1 file changed

Lines changed: 49 additions & 32 deletions

File tree

packages/sync-engine/src/supabase/edge-functions/stripe-setup.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,45 @@ async function deleteSecret(
5959
}
6060
}
6161

62+
// Thrown when setup-secret validation fails; carries the HTTP status for the response.
63+
class SetupAuthError extends Error {
64+
constructor(
65+
readonly status: number,
66+
message: string
67+
) {
68+
super(message)
69+
}
70+
}
71+
72+
// Validates the caller's bearer secret against the per-install setup secret in vault.
73+
// Returns only on a strict match with a non-empty stored secret; throws on every other case.
74+
async function validateSetupSecret(callerSecret: string): Promise<void> {
75+
const dbUrl = Deno.env.get('SUPABASE_DB_URL')
76+
if (!dbUrl) {
77+
throw new SetupAuthError(500, 'SUPABASE_DB_URL not set')
78+
}
79+
80+
let authSql: ReturnType<typeof postgres> | undefined
81+
try {
82+
authSql = postgres(dbUrl, { max: 1, prepare: false })
83+
const secretResult = await authSql`
84+
SELECT decrypted_secret
85+
FROM vault.decrypted_secrets
86+
WHERE name = ${SETUP_SECRET_NAME}
87+
`
88+
const storedSecret: unknown = secretResult[0]?.decrypted_secret
89+
if (typeof storedSecret !== 'string' || storedSecret.length === 0) {
90+
throw new SetupAuthError(500, 'Setup secret not configured in vault')
91+
}
92+
if (callerSecret === storedSecret) {
93+
return
94+
}
95+
throw new SetupAuthError(403, 'Forbidden: Invalid setup secret')
96+
} finally {
97+
if (authSql) await authSql.end()
98+
}
99+
}
100+
62101
Deno.serve(async (req) => {
63102
// Extract project ref from SUPABASE_URL (format: https://{projectRef}.{base})
64103
const supabaseUrl = Deno.env.get('SUPABASE_URL')
@@ -88,39 +127,17 @@ Deno.serve(async (req) => {
88127
// never has to trust the bearer token for privileged Management operations.
89128
const accessToken = req.headers.get('x-management-api-token') ?? ''
90129

91-
{
92-
const dbUrl = Deno.env.get('SUPABASE_DB_URL')
93-
if (!dbUrl) {
94-
return new Response(JSON.stringify({ error: 'SUPABASE_DB_URL not set' }), {
95-
status: 500,
96-
headers: { 'Content-Type': 'application/json' },
97-
})
98-
}
99-
100-
let authSql: ReturnType<typeof postgres> | undefined
101-
try {
102-
authSql = postgres(dbUrl, { max: 1, prepare: false })
103-
const secretResult = await authSql`
104-
SELECT decrypted_secret
105-
FROM vault.decrypted_secrets
106-
WHERE name = ${SETUP_SECRET_NAME}
107-
`
108-
if (secretResult.length === 0) {
109-
return new Response('Setup secret not configured in vault', { status: 500 })
110-
}
111-
if (callerSecret !== secretResult[0].decrypted_secret) {
112-
return new Response('Forbidden: Invalid setup secret', { status: 403 })
113-
}
114-
} catch (error: unknown) {
115-
const err = error as Error
116-
console.error('Setup secret validation error:', error)
117-
return new Response(JSON.stringify({ error: err.message }), {
118-
status: 500,
119-
headers: { 'Content-Type': 'application/json' },
120-
})
121-
} finally {
122-
if (authSql) await authSql.end()
130+
try {
131+
await validateSetupSecret(callerSecret)
132+
} catch (error: unknown) {
133+
if (error instanceof SetupAuthError) {
134+
return new Response(error.message, { status: error.status })
123135
}
136+
console.error('Setup secret validation error:', error)
137+
return new Response(JSON.stringify({ error: (error as Error).message }), {
138+
status: 500,
139+
headers: { 'Content-Type': 'application/json' },
140+
})
124141
}
125142

126143
// Handle GET requests for status

0 commit comments

Comments
 (0)