diff --git a/docker/docker-compose-logging.yml b/docker/docker-compose-logging.yml index 2a1cb32010c..b65d190d52e 100644 --- a/docker/docker-compose-logging.yml +++ b/docker/docker-compose-logging.yml @@ -80,7 +80,8 @@ services: options: syslog-address: "tcp://localhost:${VECTOR_PORT}" tag: meta - function: + + functions: depends_on: analytics: condition: service_healthy diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 599497604a6..0152bb6e109 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -241,13 +241,9 @@ services: PG_META_DB_USER: supabase_admin PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD} - function: - container_name: supabase-function - image: supabase/edge-runtime:v1.1.7 - depends_on: - db: - # Disable this if you are using an external Postgres database - condition: service_healthy + functions: + container_name: supabase-edge-functions + image: supabase/edge-runtime:v1.2.12 restart: unless-stopped environment: JWT_SECRET: ${JWT_SECRET} @@ -256,6 +252,12 @@ services: SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY} SUPABASE_DB_URL: postgresql://postgres:${POSTGRES_PASSWORD}@{POSTGRES_DB}:${POSTGRES_PORT}/${POSTGRES_DB}" VERIFY_JWT: false + volumes: + - ./volumes/functions:/home/deno/functions:Z + command: + - start + - --main-service + - /home/deno/functions/main # Comment out everything below this point if you are using an external Postgres database db: diff --git a/docker/volumes/functions/main/index.ts b/docker/volumes/functions/main/index.ts new file mode 100644 index 00000000000..2408aee0ff7 --- /dev/null +++ b/docker/volumes/functions/main/index.ts @@ -0,0 +1,93 @@ +import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' + +console.log('main function started') + +const JWT_SECRET = Deno.env.get('JWT_SECRET') +const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true' + +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 verifyJWT(jwt: string): Promise { + const encoder = new TextEncoder() + const secretKey = encoder.encode(JWT_SECRET) + try { + await jose.jwtVerify(jwt, secretKey) + } catch (err) { + console.error(err) + return false + } + return true +} + +serve(async (req: Request) => { + if (req.method !== 'OPTIONS' && VERIFY_JWT) { + try { + const token = getAuthToken(req) + const isValidJWT = await verifyJWT(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' }, + }) + } +})