summaryrefslogtreecommitdiff
path: root/api/auth/_verifyRegistrationResponse.ts
blob: cf74ea5e9862ae731bdd6f31cc0268080f2223d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import type Authenticator from '../types/Authenticator';
import type User from '../types/User';
import { type RegistrationResponseJSON } from '@simplewebauthn/server/script/deps';

import { verifyRegistrationResponse as innerVerifyRegistrationResponse } from '@simplewebauthn/server';
import RELYING_PARTY from './_relyingParty';
import addAuthenticator from '../db/_addAuthenticator';
import addUser from '../db/_addUser';
import env from '../_env';

/**
 * Verifies the registration response returned by @simplewebauthn/browser's
 * startAuthentication() method. Includes checking the provided challenge
 * matches the expected challenge, which should be the most recent challenge
 * to be associated with the given user.
 *
 * If verification is successful, saves the new user and their new
 * authenticator to the database.
 */
export default async function verifyRegistrationResponse(
  newUser: User,
  expectedChallenge: string,
  registrationResponse: RegistrationResponseJSON,
): Promise<boolean> {
  const verification = await innerVerifyRegistrationResponse({
    response: registrationResponse,
    expectedChallenge,
    expectedOrigin: env.ENVIRONMENT === 'prod'
      ? 'https://scotsleidassocie.org'
      : 'https://webauthn.scotsleidassocie.org',
    expectedRPID: RELYING_PARTY.id,
  }).catch((err) => {
    throw new Error(`Registration response verification failed. ${err}`);
  });

  if (!verification.verified || !verification.registrationInfo) {
    return false;
  }

  const textDecoder = new TextDecoder();

  const authenticator: Authenticator = {
    id: textDecoder.decode(verification.registrationInfo.credentialID),
    backedUp: verification.registrationInfo.credentialBackedUp,
    counter: verification.registrationInfo.counter,
    deviceType: verification.registrationInfo.credentialDeviceType,
    publicKey: textDecoder.decode(verification.registrationInfo.credentialPublicKey),
    type: verification.registrationInfo.credentialType,
    userId: newUser.id,

    // transports doesn't seem to be implemented on the RegistrationInfo object
    // provided by @simplewebauthn/server. We'll default to an empty array for
    // now, and if the property is ever implemented, if we're lucky, we might
    // get it for free. It's not clear to me how this property affects
    // behaviour, but it's not required to be populated by the WebAuthn
    // standard.
    transports: verification.registrationInfo['transports'] ?? [],
  };

  addUser(newUser);
  addAuthenticator(authenticator);
}