diff --git a/packages/api/src/routes/interactions/commands/hello-world.ts b/packages/api/src/routes/interactions/commands/hello-world.ts index 64e75e9..4741d57 100644 --- a/packages/api/src/routes/interactions/commands/hello-world.ts +++ b/packages/api/src/routes/interactions/commands/hello-world.ts @@ -10,10 +10,15 @@ export const helloWorld: InteractionHandler = ( interaction: InteractionRequest, context: Context ): InteractionResponse => { + console.log({ interaction }); return { type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, data: { - content: `Hey there, ${interaction.member?.nick || interaction.user?.username}`, + content: `Hey there, ${ + interaction.member?.nick || + interaction.member?.user?.username || + interaction.user?.username + }`, }, }; }; diff --git a/packages/api/src/routes/interactions/helpers.ts b/packages/api/src/routes/interactions/helpers.ts index aaa5a1b..fff47f0 100644 --- a/packages/api/src/routes/interactions/helpers.ts +++ b/packages/api/src/routes/interactions/helpers.ts @@ -13,27 +13,50 @@ export const verifyRequest = async ( request: Request, interaction: InteractionRequest ): Promise => { - const timestamp = request.headers.get('x-signature-timestamp'); - const signature = request.headers.get('x-signature-ed25519'); + try { + const timestamp = request.headers.get('x-signature-timestamp'); + const signature = request.headers.get('x-signature-ed25519'); - if (!timestamp || !signature) { + if (!timestamp || !signature) { + return false; + } + + const key = await crypto.subtle.importKey( + 'raw', + bufferizeHex(config.publicKey), + { name: 'NODE-ED25519', namedCurve: 'NODE-ED25519', public: true } as any, + false, + ['verify'] + ); + + const verified = await crypto.subtle.verify( + 'NODE-ED25519', + key, + bufferizeHex(signature), + bufferizeString(timestamp + JSON.stringify(interaction)) + ); + + return verified; + } catch (e) { return false; } +}; - const key = await crypto.subtle.importKey( - 'raw', - Buffer.from(config.publicKey, 'hex'), - { name: 'NODE-ED25519', namedCurve: 'NODE-ED25519', public: true } as any, - false, - ['verify'] - ); +// Cloudflare Workers + SubtleCrypto has no idea what a Buffer.from() is. +// What the fuck? +const bufferizeHex = (input: string) => { + const buffer = new Uint8Array(input.length / 2); - return crypto.subtle.verify( - 'NODE-ED25519', - key, - Buffer.from(signature, 'hex'), - Buffer.from(timestamp + JSON.stringify(interaction)) - ); + for (let i = 0; i < input.length; i += 2) { + buffer[i / 2] = parseInt(input.substring(i, i + 2), 16); + } + + return buffer; +}; + +const bufferizeString = (input: string) => { + const encoder = new TextEncoder(); + return encoder.encode(input); }; export type InteractionHandler = (( @@ -59,7 +82,7 @@ export const runAsync = async ( 'Content-Type': 'application/json', }, body: JSON.stringify({ - type: InteractionCallbackType.DEFERRED_UPDATE_MESSAGE, + type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, data: { flags: handler.ephemeral ? InteractionFlags.EPHEMERAL : 0, ...response.data, @@ -82,7 +105,7 @@ export const runAsync = async ( 'Content-Type': 'application/json', }, body: JSON.stringify({ - type: InteractionCallbackType.DEFERRED_UPDATE_MESSAGE, + type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, data: { content: "I'm sorry, I'm having trouble processing this request.", flags: InteractionFlags.EPHEMERAL, diff --git a/packages/api/src/routes/interactions/interactions.ts b/packages/api/src/routes/interactions/interactions.ts index 2899093..831fd7d 100644 --- a/packages/api/src/routes/interactions/interactions.ts +++ b/packages/api/src/routes/interactions/interactions.ts @@ -30,14 +30,17 @@ export const handleInteraction: RoleypolyHandler = async ( } if (!(await verifyRequest(context.config, request, interaction))) { + console.warn('interactions: invalid signature'); return new Response('invalid request signature', { status: 401 }); } if (interaction.type !== InteractionType.APPLICATION_COMMAND) { if (interaction.type === InteractionType.PING) { + console.info('interactions: ping'); return json({ type: InteractionCallbackType.PONG }); } + console.warn('interactions: not application command'); return json({ err: 'not implemented' }, { status: 400 }); } diff --git a/packages/api/src/utils/response.ts b/packages/api/src/utils/response.ts index e2d0824..f4719c2 100644 --- a/packages/api/src/utils/response.ts +++ b/packages/api/src/utils/response.ts @@ -17,7 +17,7 @@ export const corsHeaders = { 'Access-Control-Max-Age': '86400', }; -export const noContent = () => new Response(null, { status: 204 }); +export const noContent = () => new Response(null, { status: 204, headers: corsHeaders }); export const seeOther = (url: string) => new Response( `If you are not redirected soon, click here.`, diff --git a/terraform/interactions.tf b/terraform/interactions.tf new file mode 100644 index 0000000..7488a20 --- /dev/null +++ b/terraform/interactions.tf @@ -0,0 +1,5 @@ +resource "discord-interactions_guild_command" "hello-world" { + name = "hello-world" + description = "Says hello!" + guild_id = "386659935687147521" +}