| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- 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 BRIVEN_URL = Deno.env.get('BRIVEN_URL')
- const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
- // Create JWKS for ES256/RS256 tokens (newer tokens)
- let BRIVEN_JWT_KEYS: ReturnType<typeof jose.createRemoteJWKSet> | null = null
- if (BRIVEN_URL) {
- try {
- BRIVEN_JWT_KEYS = jose.createRemoteJWKSet(
- new URL('/auth/v1/.well-known/jwks.json', BRIVEN_URL)
- )
- } catch (e) {
- console.error('Failed to fetch JWKS from BRIVEN_URL:', e)
- }
- }
- /**
- * Extract JWT token from Authorization header
- *
- * Parses the Authorization header to extract the Bearer token.
- * Expects format: "Bearer <token>"
- *
- * @param req - The HTTP request object
- * @returns The JWT token string
- * @throws Error if Authorization header is missing or malformed
- */
- 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 (!BRIVEN_JWT_KEYS) {
- console.error('JWKS not available for ES256/RS256 token verification')
- return false
- }
- try {
- await jose.jwtVerify(jwt, BRIVEN_JWT_KEYS)
- } catch (e) {
- console.error('Asymmetric JWT verification error', e);
- return false
- }
- return true;
- }
- /**
- * Verify JWT token, handling both legacy (HS256) and newer (ES256/RS256) algorithms
- *
- * This function automatically detects the algorithm used in the token and applies
- * the appropriate verification method:
- * - HS256: Uses JWT_SECRET (symmetric key)
- * - ES256/RS256: Uses JWKS endpoint (asymmetric public keys)
- *
- * This fix ensures compatibility with both legacy tokens and newer asymmetric tokens,
- * resolving the "Key for the ES256 algorithm must be of type CryptoKey" error.
- *
- * @param jwt - The JWT token string to verify
- * @returns Promise resolving to true if verification succeeds, false otherwise
- */
- 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' },
- })
- }
- })
|