| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- // src/webauthn/webauthn.service.ts
- import {
- generateRegistrationOptions,
- verifyRegistrationResponse,
- generateAuthenticationOptions,
- verifyAuthenticationResponse,
- VerifiedRegistrationResponse,
- VerifiedAuthenticationResponse,
- RegistrationResponseJSON,
- } from '@simplewebauthn/server';
- import { Injectable, Logger } from '@nestjs/common';
- import { isoUint8Array } from '@simplewebauthn/server/helpers';
- import { isoBase64URL } from '@simplewebauthn/server/helpers';
- import { serverConfig } from 'src/config/config';
- @Injectable()
- export class WebauthnService {
- private logger: Logger = new Logger(`WebAuthn`)
- private rpName = serverConfig.rpName
- private rpID = serverConfig.rpId
- private origin = serverConfig.origin
- public devices: any[] = []; // In-memory device store
- constructor() {
- this.logger.log(`Web Authn service instantiated...`)
- }
- async generateRegistrationOptions(user: {
- id: string;
- username: string;
- devices: {
- credentialID: Uint8Array;
- transports?: AuthenticatorTransport[];
- }[];
- }) {
- return generateRegistrationOptions({
- rpName: this.rpName,
- rpID: this.rpID,
- userID: isoUint8Array.fromUTF8String(user.id),
- userName: user.username,
- timeout: 60000,
- attestationType: 'none',
- excludeCredentials: user.devices.map(dev => ({
- id: isoBase64URL.fromBuffer(dev.credentialID),
- type: 'public-key',
- transports: dev.transports,
- })),
- authenticatorSelection: {
- userVerification: 'preferred',
- residentKey: 'preferred',
- },
- });
- }
- async verifyRegistrationResponse(
- responseBody: RegistrationResponseJSON,
- expectedChallenge: string,
- ): Promise<VerifiedRegistrationResponse> {
- this.logger.log('Verifying registration response...');
- this.logger.debug(JSON.stringify(responseBody, null, 2));
- return verifyRegistrationResponse({
- response: responseBody,
- expectedChallenge,
- expectedOrigin: this.origin,
- expectedRPID: this.rpID,
- });
- }
- async generateAuthenticationOptions(registeredDevices?: {
- credentialID: Uint8Array;
- transports?: AuthenticatorTransport[];
- }[]) {
- const allowCredentials = registeredDevices?.length
- ? registeredDevices.map(dev => ({
- id: isoBase64URL.fromBuffer(dev.credentialID),
- type: 'public-key',
- transports: dev.transports,
- }))
- : undefined; // 💡 omit if no devices or we want discoverable login
- return generateAuthenticationOptions({
- rpID: this.rpID,
- timeout: 60000,
- userVerification: 'preferred',
- allowCredentials, // ✅ undefined = allow browser to try passkeys
- });
- }
- async verifyAuthenticationResponse(
- responseBody: any,
- expectedChallenge: string,
- credentialPublicKey: Uint8Array,
- credentialID: string, // stored as base64url in DB
- counter: number,
- ): Promise<VerifiedAuthenticationResponse> {
- return verifyAuthenticationResponse({
- response: responseBody,
- expectedChallenge,
- expectedOrigin: this.origin,
- expectedRPID: this.rpID,
- credential: {
- id: credentialID, // must be base64url string
- publicKey: credentialPublicKey,
- counter,
- },
- });
- }
- storeDeviceInfo(deviceInfo: any): void {
- this.devices.push(deviceInfo)
- console.log(this.devices)
- }
- findDeviceByCredentialID(
- devices: {
- credentialID: Uint8Array;
- credentialPublicKey: Uint8Array;
- counter: number;
- transports?: AuthenticatorTransport[];
- }[],
- credentialID: string, // base64url
- ) {
- return devices.find(dev =>
- isoBase64URL.fromBuffer(dev.credentialID) === credentialID
- );
- }
- }
|