Simplify Self Hosting of Edge Functions (#253)

Until now we had a `find` command to delete existing edge functions
before copying them for the self hosting set up. This command is now
removed, because we also moved the `main` function to the existing
functions directory and just not push it as function to Supabase.
This commit is contained in:
Rico Berger
2025-04-26 15:38:59 +02:00
committed by GitHub
parent 2270943066
commit 8fc68951b0
4 changed files with 102 additions and 98 deletions

View File

@@ -0,0 +1,99 @@
import { serve } from "https://deno.land/std@0.131.0/http/server.ts";
import * as jose from "https://deno.land/x/jose@v4.14.4/index.ts";
/**
* The `main` function is only used for the self-hosted FeedDeck setup, so that
* the edge functions are working. For FeedDeck hosted on the Supabase Cloud
* offering the function is ignored and not deployed.
*/
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<boolean> {
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" },
});
}
});

View File

@@ -1,6 +1,5 @@
!volumes/db/_supabase.sql
volumes/db/data
volumes/functions/*
!volumes/functions/main
volumes/functions
volumes/storage
.env

View File

@@ -18,8 +18,7 @@ be found in the FeedDeck contributing guide and the Supabase documentation.
2. Copy all edge functions into the correct directory:
```sh
find ./volumes/functions -maxdepth 1 ! -name main -exec rm -rf {} +
cp -a ../functions/. volumes/functions/
cp -r ../functions/. volumes/functions/
```
3. Create a `.env` file and adjust the environment variables as needed:
@@ -59,4 +58,5 @@ be found in the FeedDeck contributing guide and the Supabase documentation.
docker compose down -v
rm -rf volumes/db/data/
rm -rf volumes/storage/
rm -rf volumes/functions/
```

View File

@@ -1,94 +0,0 @@
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
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 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<boolean> {
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' },
})
}
})