import { generateLoginChallenge , generateRegistrationChallenge , parseLoginRequest , parseRegisterRequest , verifyAuthenticatorAssertion } from "@webauthn/server" ; import { isEmpty } from "lodash" ; import { isNullOrUndefined } from "util" ; import { ForbiddenProblem , MissingRequiredInputsProblem , Problem , UnauthorizedProblem , BadRequestProblem } from "../../../domain/entities/Problem" ; import IAuthnConfig from "../../../interfaces/security/IAuthnConfig" ; import { IUserRepository } from "../../repositories/IUserRepository" ; import { IJwtProvider } from "../../security/IJwtProvider" ; import { User } from "../../../domain/entities/User" ; import base64url from 'base64url' ; /**
 * Passwordless authentication with FIDO2 standard
 */ export default class Fido2Authenticate { private readonly userRepository : IUserRepository ; private readonly authnConfig : IAuthnConfig ; private readonly jwtProvider : IJwtProvider ; constructor ( userRepository , authnConfig , jwtProvider ) { this . userRepository = userRepository , this . authnConfig = authnConfig , this . jwtProvider = jwtProvider ; } /**
     * Generate attestation options to challenge register request with passwordless authentication
    */ public async generateAttestationOptions ( email , passwordless ) { if ( passwordless && this . authnConfig . enablePasswordless == false ) { return new ForbiddenProblem ( { detail : "Passwordless authentication is not enabled." } ) ; } if ( ! passwordless && this . authnConfig . enable2FAWithFido2 == false ) { return new ForbiddenProblem ( { detail : "Second authentication with FIDO 2 is not enabled." } ) ; } if ( isEmpty ( email ) ) { return new MissingRequiredInputsProblem ( { detail : "missing username." } ) ; } const attestationChallengeResponse = generateRegistrationChallenge ( { relyingParty : { name : 'clean-starter' , id : "localhost" } , user : { id : "webauthn_uuid" , name : email } } ) ; let user = await this . userRepository . loadByEmail ( email ) ; if ( passwordless ) { if ( isNullOrUndefined ( user ) ) { user = await this . userRepository . create ( email , attestationChallengeResponse . challenge ) ; } } this . userRepository . updateUserChallenge ( user , attestationChallengeResponse . challenge ) ; return attestationChallengeResponse ; } param attestation returns public async validateAttestation ( attestation ) { if ( this . authnConfig . enablePasswordless == false ) { return new ForbiddenProblem ( { detail : "Passwordless authentication is not enabled." } ) ; } const { key , challenge } = parseRegisterRequest ( attestation ) ; if ( isNullOrUndefined ( challenge ) ) { return new UnauthorizedProblem ( { detail : 'Invalid register request' } ) ; } const user = await this . userRepository . loadByChallenge ( challenge ) ; if ( isNullOrUndefined ( user ) ) { return new UnauthorizedProblem ( { detail : 'Invalid register request' } ) ; } this . userRepository . addKeyToUser ( user , key ) ; return { registerStatus : true } ; } param email returns public async generateAssertionOptions ( email ) { if ( this . authnConfig . enablePasswordless == false ) { return new ForbiddenProblem ( { detail : "Passwordless authentication is not enabled." } ) ; } if ( isEmpty ( email ) ) { return new MissingRequiredInputsProblem ; } const user = await this . userRepository . loadByEmail ( email ) ; if ( isNullOrUndefined ( user ) ) { return new UnauthorizedProblem ( { detail : 'Invalid credentials' } ) ; } const assertionChallenge = generateLoginChallenge ( user . key ) ; this . userRepository . updateUserChallenge ( user , assertionChallenge . challenge ) ; return assertionChallenge ; } param credentials returns public async validateAssertion ( credentials ) { if ( this . authnConfig . enablePasswordless == false ) { return new ForbiddenProblem ( { detail : "Passwordless authentication is not enabled." } ) ; } const { challenge , keyId } = parseLoginRequest ( credentials ) ; if ( isNullOrUndefined ( challenge ) ) { return new UnauthorizedProblem ( { detail : 'Invalid challenge' } ) ; } const user = await this . userRepository . loadByChallenge ( challenge ) ; if ( isNullOrUndefined ( user ) || isNullOrUndefined ( user . key ) || ( user . key . credID !== keyId ) ) { return new UnauthorizedProblem ( { detail : 'Invalid login challenge request' } ) ; } const result = verifyAuthenticatorAssertion ( credentials , user . key ) ; this . userRepository . updateUserChallenge ( user , "" ) ; return { token : this . jwtProvider . generateToken ( { id : user . id } ) , status : true } ; } }