mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-24 19:39:11 +00:00
nevermind x2, tweetnacl is bad but SubtleCrypto has what we need apparently
This commit is contained in:
parent
ea691bb56e
commit
6583471f40
6 changed files with 50 additions and 20 deletions
|
@ -5,8 +5,10 @@ import { authCallback } from '@roleypoly/api/src/routes/auth/callback';
|
|||
import { authSessionDelete } from '@roleypoly/api/src/routes/auth/delete-session';
|
||||
import { authSession } from '@roleypoly/api/src/routes/auth/session';
|
||||
import { guildsGuild } from '@roleypoly/api/src/routes/guilds/guild';
|
||||
import { guildsRolesPut } from '@roleypoly/api/src/routes/guilds/guild-roles-put';
|
||||
import { guildsGuildPatch } from '@roleypoly/api/src/routes/guilds/guilds-patch';
|
||||
import { guildsSlug } from '@roleypoly/api/src/routes/guilds/slug';
|
||||
import { handleInteraction } from '@roleypoly/api/src/routes/interactions/interactions';
|
||||
import {
|
||||
requireSession,
|
||||
withAuthMode,
|
||||
|
@ -33,14 +35,12 @@ const guildsCommon = [injectParams, withSession, requireSession, requireMember];
|
|||
router.get('/guilds/:guildId', ...guildsCommon, guildsGuild);
|
||||
router.patch('/guilds/:guildId', ...guildsCommon, requireEditor, guildsGuildPatch);
|
||||
router.delete('/guilds/:guildId/cache', ...guildsCommon, requireEditor, notImplemented);
|
||||
router.put('/guilds/:guildId/roles', ...guildsCommon, notImplemented);
|
||||
router.put('/guilds/:guildId/roles', ...guildsCommon, guildsRolesPut);
|
||||
|
||||
// Slug is unauthenticated...
|
||||
router.get('/guilds/slug/:guildId', injectParams, guildsSlug);
|
||||
|
||||
// TODO: move this to another worker.
|
||||
// It inflates the output by way too much.
|
||||
// router.post('/interactions', handleInteraction);
|
||||
router.post('/interactions', handleInteraction);
|
||||
|
||||
router.get(
|
||||
'/legacy/preflight/:guildId',
|
||||
|
|
5
packages/api/src/routes/guilds/guild-roles-put.spec.ts
Normal file
5
packages/api/src/routes/guilds/guild-roles-put.spec.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
describe('PUT /guilds/:id/roles', () => {
|
||||
it('returns Not Implemented when called', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
9
packages/api/src/routes/guilds/guild-roles-put.ts
Normal file
9
packages/api/src/routes/guilds/guild-roles-put.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { Context, RoleypolyHandler } from '@roleypoly/api/src/utils/context';
|
||||
import { notImplemented } from '@roleypoly/api/src/utils/response';
|
||||
|
||||
export const guildsRolesPut: RoleypolyHandler = async (
|
||||
request: Request,
|
||||
context: Context
|
||||
) => {
|
||||
return notImplemented();
|
||||
};
|
|
@ -3,8 +3,16 @@ import nacl from 'tweetnacl';
|
|||
import { configContext } from '../../utils/testHelpers';
|
||||
import { verifyRequest } from './helpers';
|
||||
|
||||
//
|
||||
// Q: Why tweetnacl when WebCrypto is available?
|
||||
// A: Discord uses tweetnacl on their end, thus is also
|
||||
// used in far more examples of Discord Interactions than WebCrypto.
|
||||
// We don't actually use it in Workers, as SubtleCrypto using NODE-ED25519
|
||||
// is better in every way, and still gives us the same effect.
|
||||
//
|
||||
|
||||
describe('verifyRequest', () => {
|
||||
it('validates a successful Discord interactions request', () => {
|
||||
it('validates a successful Discord interactions request', async () => {
|
||||
const [config, context] = configContext();
|
||||
|
||||
const timestamp = String(Date.now());
|
||||
|
@ -32,10 +40,10 @@ describe('verifyRequest', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(verifyRequest(context.config, request, body)).toBe(true);
|
||||
expect(await verifyRequest(context.config, request, body)).toBe(true);
|
||||
});
|
||||
|
||||
it('fails to validate a headerless Discord interactions request', () => {
|
||||
it('fails to validate a headerless Discord interactions request', async () => {
|
||||
const [config, context] = configContext();
|
||||
|
||||
const body: InteractionRequest = {
|
||||
|
@ -55,10 +63,10 @@ describe('verifyRequest', () => {
|
|||
headers: {},
|
||||
});
|
||||
|
||||
expect(verifyRequest(context.config, request, body)).toBe(false);
|
||||
expect(await verifyRequest(context.config, request, body)).toBe(false);
|
||||
});
|
||||
|
||||
it('fails to validate a bad signature from Discord', () => {
|
||||
it('fails to validate a bad signature from Discord', async () => {
|
||||
const [config, context] = configContext();
|
||||
|
||||
const timestamp = String(Date.now());
|
||||
|
@ -87,10 +95,10 @@ describe('verifyRequest', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(verifyRequest(context.config, request, body)).toBe(false);
|
||||
expect(await verifyRequest(context.config, request, body)).toBe(false);
|
||||
});
|
||||
|
||||
it('fails to validate when signature differs from data', () => {
|
||||
it('fails to validate when signature differs from data', async () => {
|
||||
const [config, context] = configContext();
|
||||
|
||||
const timestamp = String(Date.now());
|
||||
|
@ -118,6 +126,6 @@ describe('verifyRequest', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(verifyRequest(context.config, request, body)).toBe(false);
|
||||
expect(await verifyRequest(context.config, request, body)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { Config } from '@roleypoly/api/src/utils/config';
|
||||
import { InteractionRequest } from '@roleypoly/types';
|
||||
import nacl from 'tweetnacl';
|
||||
|
||||
export const verifyRequest = (
|
||||
export const verifyRequest = async (
|
||||
config: Config,
|
||||
request: Request,
|
||||
interaction: InteractionRequest
|
||||
): boolean => {
|
||||
): Promise<boolean> => {
|
||||
const timestamp = request.headers.get('x-signature-timestamp');
|
||||
const signature = request.headers.get('x-signature-ed25519');
|
||||
|
||||
|
@ -14,12 +13,21 @@ export const verifyRequest = (
|
|||
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']
|
||||
);
|
||||
|
||||
if (
|
||||
!nacl.sign.detached.verify(
|
||||
Buffer.from(timestamp + JSON.stringify(interaction)),
|
||||
!(await crypto.subtle.verify(
|
||||
'NODE-ED25519',
|
||||
key,
|
||||
Buffer.from(signature, 'hex'),
|
||||
Buffer.from(config.publicKey, 'hex')
|
||||
)
|
||||
Buffer.from(timestamp + JSON.stringify(interaction))
|
||||
))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ export const handleInteraction: RoleypolyHandler = async (
|
|||
return invalid();
|
||||
}
|
||||
|
||||
if (!verifyRequest(context.config, request, interaction)) {
|
||||
if (!(await verifyRequest(context.config, request, interaction))) {
|
||||
return new Response('invalid request signature', { status: 401 });
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue