From bbd13453b203e391d5b182c7c1a594f4da0abc1f Mon Sep 17 00:00:00 2001 From: William Oldham Date: Fri, 3 Nov 2023 18:07:26 +0000 Subject: [PATCH] Extract challenge verification logic to service --- src/routes/auth/manage.ts | 39 ++++++++------------------------------- src/services/challenge.ts | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 31 deletions(-) create mode 100644 src/services/challenge.ts diff --git a/src/routes/auth/manage.ts b/src/routes/auth/manage.ts index 6e60976..31bd812 100644 --- a/src/routes/auth/manage.ts +++ b/src/routes/auth/manage.ts @@ -1,4 +1,4 @@ -import { ChallengeCode, formatChallengeCode } from '@/db/models/ChallengeCode'; +import { ChallengeCode } from '@/db/models/ChallengeCode'; import { formatSession } from '@/db/models/Session'; import { User, formatUser } from '@/db/models/User'; import { getMetrics } from '@/modules/metrics'; @@ -7,10 +7,7 @@ import { handle } from '@/services/handler'; import { makeRouter } from '@/services/router'; import { makeSession, makeSessionToken } from '@/services/session'; import { z } from 'zod'; -import { nanoid } from 'nanoid'; -import forge from 'node-forge'; -import { StatusError } from '@/services/error'; -import { t } from '@mikro-orm/core'; +import { assertChallengeCode } from '@/services/challenge'; const startSchema = z.object({ captchaToken: z.string().optional(), @@ -54,32 +51,12 @@ export const manageAuthRouter = makeRouter((app) => { '/auth/register/complete', { schema: { body: completeSchema } }, handle(async ({ em, body, req }) => { - const now = Date.now(); - - const challenge = await em.findOne(ChallengeCode, { - code: body.challenge.code, - }); - - if (!challenge) throw new StatusError('Challenge Code Invalid', 401); - - if (challenge.expiresAt.getTime() <= now) - throw new StatusError('Challenge Code Expired', 401); - - const verifiedChallenge = forge.pki.ed25519.verify({ - publicKey: new forge.util.ByteStringBuffer( - Buffer.from(body.publicKey, 'base64url'), - ), - encoding: 'utf8', - signature: new forge.util.ByteStringBuffer( - Buffer.from(body.challenge.signature, 'base64url'), - ), - message: body.challenge.code, - }); - - if (!verifiedChallenge) - throw new StatusError('Challenge Code Signature Invalid', 401); - - em.remove(challenge); + await assertChallengeCode( + em, + body.challenge.code, + body.publicKey, + body.challenge.signature, + ); const user = new User(); user.namespace = body.namespace; diff --git a/src/services/challenge.ts b/src/services/challenge.ts new file mode 100644 index 0000000..f607a49 --- /dev/null +++ b/src/services/challenge.ts @@ -0,0 +1,38 @@ +import { ChallengeCode } from '@/db/models/ChallengeCode'; +import { StatusError } from '@/services/error'; +import { EntityManager } from '@mikro-orm/core'; +import forge from 'node-forge'; + +export async function assertChallengeCode( + em: EntityManager, + code: string, + publicKey: string, + signature: string, +) { + const now = Date.now(); + + const challenge = await em.findOne(ChallengeCode, { + code, + }); + + if (!challenge) throw new StatusError('Challenge Code Invalid', 401); + + if (challenge.expiresAt.getTime() <= now) + throw new StatusError('Challenge Code Expired', 401); + + const verifiedChallenge = forge.pki.ed25519.verify({ + publicKey: new forge.util.ByteStringBuffer( + Buffer.from(publicKey, 'base64url'), + ), + encoding: 'utf8', + signature: new forge.util.ByteStringBuffer( + Buffer.from(signature, 'base64url'), + ), + message: code, + }); + + if (!verifiedChallenge) + throw new StatusError('Challenge Code Signature Invalid', 401); + + em.remove(challenge); +}