feat: fix supabase
This commit is contained in:
parent
d611a1ff05
commit
541d2fc43d
2 changed files with 165 additions and 0 deletions
|
|
@ -74,6 +74,27 @@ in
|
||||||
ExecStart = "${envGenerateScript} ${config.sops.secrets.supabase-env.path}";
|
ExecStart = "${envGenerateScript} ${config.sops.secrets.supabase-env.path}";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
# Seed the edge-runtime's bootstrap `main` function. The container's
|
||||||
|
# entrypoint requires `/home/deno/functions/main/index.ts` to exist;
|
||||||
|
# without it edge-runtime fails with "could not find an appropriate
|
||||||
|
# entrypoint". Re-seed on every activation so updates to the bootstrap
|
||||||
|
# are picked up, while leaving user-authored functions untouched.
|
||||||
|
supabase-functions-seed = {
|
||||||
|
description = "Seed Supabase edge-functions main bootstrap";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
install -d -m 0755 /var/lib/supabase/functions/main
|
||||||
|
install -m 0644 ${./functions/main/index.ts} /var/lib/supabase/functions/main/index.ts
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
podman-supabase-functions = {
|
||||||
|
after = [ "supabase-functions-seed.service" ];
|
||||||
|
requires = [ "supabase-functions-seed.service" ];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
144
hosts/web-arm/modules/supabase/functions/main/index.ts
Normal file
144
hosts/web-arm/modules/supabase/functions/main/index.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
|
||||||
|
|
||||||
|
console.log('main function started')
|
||||||
|
|
||||||
|
const JWT_SECRET = Deno.env.get('JWT_SECRET')
|
||||||
|
const SUPABASE_URL = Deno.env.get('SUPABASE_URL')
|
||||||
|
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
|
||||||
|
|
||||||
|
// Create JWKS for ES256/RS256 tokens (newer tokens)
|
||||||
|
let SUPABASE_JWT_KEYS: ReturnType<typeof jose.createRemoteJWKSet> | null = null
|
||||||
|
if (SUPABASE_URL) {
|
||||||
|
try {
|
||||||
|
SUPABASE_JWT_KEYS = jose.createRemoteJWKSet(
|
||||||
|
new URL('/auth/v1/.well-known/jwks.json', SUPABASE_URL)
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to fetch JWKS from SUPABASE_URL:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAuthToken(req: Request) {
|
||||||
|
const authHeader = req.headers.get('authorization')
|
||||||
|
if (!authHeader) {
|
||||||
|
throw new Error('Missing authorization header')
|
||||||
|
}
|
||||||
|
const [bearer, token] = authHeader.split(' ')
|
||||||
|
if (bearer !== 'Bearer') {
|
||||||
|
throw new Error(`Auth header is not 'Bearer {token}'`)
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isValidLegacyJWT(jwt: string): Promise<boolean> {
|
||||||
|
if (!JWT_SECRET) {
|
||||||
|
console.error('JWT_SECRET not available for HS256 token verification')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const secretKey = encoder.encode(JWT_SECRET)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await jose.jwtVerify(jwt, secretKey);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Symmetric Legacy JWT verification error', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isValidJWT(jwt: string): Promise<boolean> {
|
||||||
|
if (!SUPABASE_JWT_KEYS) {
|
||||||
|
console.error('JWKS not available for ES256/RS256 token verification')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await jose.jwtVerify(jwt, SUPABASE_JWT_KEYS)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Asymmetric JWT verification error', e);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isValidHybridJWT(jwt: string): Promise<boolean> {
|
||||||
|
const { alg: jwtAlgorithm } = jose.decodeProtectedHeader(jwt)
|
||||||
|
|
||||||
|
if (jwtAlgorithm === 'HS256') {
|
||||||
|
console.log(`Legacy token type detected, attempting ${jwtAlgorithm} verification.`)
|
||||||
|
|
||||||
|
return await isValidLegacyJWT(jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jwtAlgorithm === 'ES256' || jwtAlgorithm === 'RS256') {
|
||||||
|
return await isValidJWT(jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.serve(async (req: Request) => {
|
||||||
|
if (req.method !== 'OPTIONS' && VERIFY_JWT) {
|
||||||
|
try {
|
||||||
|
const token = getAuthToken(req)
|
||||||
|
const isValidJWT = await isValidHybridJWT(token);
|
||||||
|
|
||||||
|
if (!isValidJWT) {
|
||||||
|
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
|
||||||
|
status: 401,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return new Response(JSON.stringify({ msg: e.toString() }), {
|
||||||
|
status: 401,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(req.url)
|
||||||
|
const { pathname } = url
|
||||||
|
const path_parts = pathname.split('/')
|
||||||
|
const service_name = path_parts[1]
|
||||||
|
|
||||||
|
if (!service_name || service_name === '') {
|
||||||
|
const error = { msg: 'missing function name in request' }
|
||||||
|
return new Response(JSON.stringify(error), {
|
||||||
|
status: 400,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const servicePath = `/home/deno/functions/${service_name}`
|
||||||
|
console.error(`serving the request with ${servicePath}`)
|
||||||
|
|
||||||
|
const memoryLimitMb = 150
|
||||||
|
const workerTimeoutMs = 1 * 60 * 1000
|
||||||
|
const noModuleCache = false
|
||||||
|
const importMapPath = null
|
||||||
|
const envVarsObj = Deno.env.toObject()
|
||||||
|
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])
|
||||||
|
|
||||||
|
try {
|
||||||
|
const worker = await EdgeRuntime.userWorkers.create({
|
||||||
|
servicePath,
|
||||||
|
memoryLimitMb,
|
||||||
|
workerTimeoutMs,
|
||||||
|
noModuleCache,
|
||||||
|
importMapPath,
|
||||||
|
envVars,
|
||||||
|
})
|
||||||
|
return await worker.fetch(req)
|
||||||
|
} catch (e) {
|
||||||
|
const error = { msg: e.toString() }
|
||||||
|
return new Response(JSON.stringify(error), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue