import * as jose from 'jose'; import { Setting } from 'wirejs-resources'; type Operation = "EVENT_CONNECT" | "EVENT_SUBSCRIBE" | "EVENT_PUBLISH"; type AuthorizationRequest = { authorizationToken: string, requestContext: { apiId: string, accountId: string, requestId: string, operation: Operation, channelNamespaceName: string, channel: string }, requestHeaders: Record }; type AuthorizationResult = { isAuthorized: boolean; handlerContext?: Record; // unused by wirejs ttlOverride?: number; }; let secret: Promise | undefined = undefined; async function getSecret(ns: string): Promise { if (!secret) { secret = new Promise(async (resolve, reject) => { try { const secret = new Setting( process.env.SECRET_SCOPE!, process.env.SECRET_ID! ) resolve((await secret.read())!); } catch (error) { console.error('Error reading secret:', error); reject(error); } }); } return secret; } export const handler = async (event: AuthorizationRequest): Promise => { /** * Intended authorization rules: * * 1. Must be connect or subscribe operation * 2. Must be a valid JWT token * 3. Must be signed with the correct secret * 4. Must be for the correct channel */ try { console.log( 'Authorization request:', event.requestContext.operation, event.requestContext.channel ); if (event.requestContext.operation === 'EVENT_PUBLISH') { throw new Error('Publish not permitted'); } const token = event.authorizationToken; if (!token) { throw new Error('Authorization token is missing'); } const decoded = await jose.jwtVerify( token, new TextEncoder().encode(await getSecret(event.requestContext.channelNamespaceName)) ); // channel from context gets prefixed with a slash if (event.requestContext.channel && `/${decoded.payload.channel}` !== event.requestContext.channel ) { throw new Error(`Channel mismatch: Token is for "/${decoded.payload.channel}", authorization request is for "${event.requestContext.channel}".`); } console.log('Authorized.'); // Default TTL from AppSync is 5 minutes according to construct docstring. return { isAuthorized: true, } } catch (error) { console.error('Authorization error:', error); return { isAuthorized: false, } } };