diff --git a/src/abstractions/api.service.ts b/src/abstractions/api.service.ts index 2994ba4296..4de63ddd10 100644 --- a/src/abstractions/api.service.ts +++ b/src/abstractions/api.service.ts @@ -62,8 +62,8 @@ import { UpdateProfileRequest } from '../models/request/updateProfileRequest'; import { UpdateTwoFactorAuthenticatorRequest } from '../models/request/updateTwoFactorAuthenticatorRequest'; import { UpdateTwoFactorDuoRequest } from '../models/request/updateTwoFactorDuoRequest'; import { UpdateTwoFactorEmailRequest } from '../models/request/updateTwoFactorEmailRequest'; -import { UpdateTwoFactorU2fDeleteRequest } from '../models/request/updateTwoFactorU2fDeleteRequest'; -import { UpdateTwoFactorU2fRequest } from '../models/request/updateTwoFactorU2fRequest'; +import { UpdateTwoFactorWebAuthnDeleteRequest } from '../models/request/updateTwoFactorWebAuthnDeleteRequest'; +import { UpdateTwoFactorWebAuthnRequest } from '../models/request/updateTwoFactorWebAuthnRequest'; import { UpdateTwoFactorYubioOtpRequest } from '../models/request/updateTwoFactorYubioOtpRequest'; import { VerifyBankRequest } from '../models/request/verifyBankRequest'; import { VerifyDeleteRecoverRequest } from '../models/request/verifyDeleteRecoverRequest'; @@ -117,10 +117,7 @@ import { TwoFactorDuoResponse } from '../models/response/twoFactorDuoResponse'; import { TwoFactorEmailResponse } from '../models/response/twoFactorEmailResponse'; import { TwoFactorProviderResponse } from '../models/response/twoFactorProviderResponse'; import { TwoFactorRecoverResponse } from '../models/response/twoFactorRescoverResponse'; -import { - ChallengeResponse, - TwoFactorU2fResponse, -} from '../models/response/twoFactorU2fResponse'; +import { ChallengeResponse, TwoFactorWebAuthnResponse } from '../models/response/twoFactorWebAuthnResponse'; import { TwoFactorYubiKeyResponse } from '../models/response/twoFactorYubiKeyResponse'; import { UserKeyResponse } from '../models/response/userKeyResponse'; @@ -274,8 +271,8 @@ export abstract class ApiService { getTwoFactorOrganizationDuo: (organizationId: string, request: PasswordVerificationRequest) => Promise; getTwoFactorYubiKey: (request: PasswordVerificationRequest) => Promise; - getTwoFactorU2f: (request: PasswordVerificationRequest) => Promise; - getTwoFactorU2fChallenge: (request: PasswordVerificationRequest) => Promise; + getTwoFactorWebAuthn: (request: PasswordVerificationRequest) => Promise; + getTwoFactorWebAuthnChallenge: (request: PasswordVerificationRequest) => Promise; getTwoFactorRecover: (request: PasswordVerificationRequest) => Promise; putTwoFactorAuthenticator: ( request: UpdateTwoFactorAuthenticatorRequest) => Promise; @@ -284,8 +281,8 @@ export abstract class ApiService { putTwoFactorOrganizationDuo: (organizationId: string, request: UpdateTwoFactorDuoRequest) => Promise; putTwoFactorYubiKey: (request: UpdateTwoFactorYubioOtpRequest) => Promise; - putTwoFactorU2f: (request: UpdateTwoFactorU2fRequest) => Promise; - deleteTwoFactorU2f: (request: UpdateTwoFactorU2fDeleteRequest) => Promise; + putTwoFactorWebAuthn: (request: UpdateTwoFactorWebAuthnRequest) => Promise; + deleteTwoFactorWebAuthn: (request: UpdateTwoFactorWebAuthnDeleteRequest) => Promise; putTwoFactorDisable: (request: TwoFactorProviderRequest) => Promise; putTwoFactorOrganizationDisable: (organizationId: string, request: TwoFactorProviderRequest) => Promise; diff --git a/src/abstractions/auth.service.ts b/src/abstractions/auth.service.ts index d5041720ce..ac7ef048d0 100644 --- a/src/abstractions/auth.service.ts +++ b/src/abstractions/auth.service.ts @@ -27,7 +27,7 @@ export abstract class AuthService { twoFactorToken: string, remember?: boolean) => Promise; logOut: (callback: Function) => void; getSupportedTwoFactorProviders: (win: Window) => any[]; - getDefaultTwoFactorProvider: (u2fSupported: boolean) => TwoFactorProviderType; + getDefaultTwoFactorProvider: (webAuthnSupported: boolean) => TwoFactorProviderType; makePreloginKey: (masterPassword: string, email: string) => Promise; authingWithApiKey: () => boolean; authingWithSso: () => boolean; diff --git a/src/abstractions/platformUtils.service.ts b/src/abstractions/platformUtils.service.ts index 5cd80c8d98..415151d3ac 100644 --- a/src/abstractions/platformUtils.service.ts +++ b/src/abstractions/platformUtils.service.ts @@ -21,7 +21,7 @@ export abstract class PlatformUtilsService { launchUri: (uri: string, options?: any) => void; saveFile: (win: Window, blobData: any, blobOptions: any, fileName: string) => void; getApplicationVersion: () => string; - supportsU2f: (win: Window) => boolean; + supportsWebAuthn: (win: Window) => boolean; supportsDuo: () => boolean; showToast: (type: 'error' | 'success' | 'warning' | 'info', title: string, text: string | string[], options?: any) => void; diff --git a/src/angular/components/two-factor.component.ts b/src/angular/components/two-factor.component.ts index 440f5efb6e..3a2fa77b54 100644 --- a/src/angular/components/two-factor.component.ts +++ b/src/angular/components/two-factor.component.ts @@ -26,18 +26,18 @@ import { TwoFactorProviders } from '../../services/auth.service'; import { ConstantsService } from '../../services/constants.service'; import * as DuoWebSDK from 'duo_web_sdk'; -import { U2f } from '../../misc/u2f'; +import { WebAuthn } from '../../misc/webauthn'; export class TwoFactorComponent implements OnInit, OnDestroy { token: string = ''; remember: boolean = false; - u2fReady: boolean = false; - initU2f: boolean = true; + webAuthnReady: boolean = false; + webAuthnNewTab: boolean = false; providers = TwoFactorProviders; providerType = TwoFactorProviderType; selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator; - u2fSupported: boolean = false; - u2f: U2f = null; + webAuthnSupported: boolean = false; + webAuthn: WebAuthn = null; title: string = ''; twoFactorEmail: string = null; formPromise: Promise; @@ -54,7 +54,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy { protected platformUtilsService: PlatformUtilsService, protected win: Window, protected environmentService: EnvironmentService, protected stateService: StateService, protected storageService: StorageService, protected route: ActivatedRoute) { - this.u2fSupported = this.platformUtilsService.supportsU2f(win); + this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); } async ngOnInit() { @@ -77,33 +77,32 @@ export class TwoFactorComponent implements OnInit, OnDestroy { this.successRoute = 'lock'; } - if (this.initU2f && this.win != null && this.u2fSupported) { - let customWebVaultUrl: string = null; - if (this.environmentService.baseUrl != null) { - customWebVaultUrl = this.environmentService.baseUrl; - } else if (this.environmentService.webVaultUrl != null) { - customWebVaultUrl = this.environmentService.webVaultUrl; + if (this.win != null && this.webAuthnSupported) { + let webVaultUrl = this.environmentService.getWebVaultUrl(); + if (webVaultUrl == null) { + webVaultUrl = 'https://vault.bitwarden.com'; } - - this.u2f = new U2f(this.win, customWebVaultUrl, (token: string) => { - this.token = token; - this.submit(); - }, (error: string) => { - this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error); - }, (info: string) => { - if (info === 'ready') { - this.u2fReady = true; + this.webAuthn = new WebAuthn(this.win, webVaultUrl, this.webAuthnNewTab, this.platformUtilsService, + this.i18nService, (token: string) => { + this.token = token; + this.submit(); + }, (error: string) => { + this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error); + }, (info: string) => { + if (info === 'ready') { + this.webAuthnReady = true; + } } - }); + ); } - this.selectedProviderType = this.authService.getDefaultTwoFactorProvider(this.u2fSupported); + this.selectedProviderType = this.authService.getDefaultTwoFactorProvider(this.webAuthnSupported); await this.init(); } ngOnDestroy(): void { - this.cleanupU2f(); - this.u2f = null; + this.cleanupWebAuthn(); + this.webAuthn = null; } async init() { @@ -112,35 +111,18 @@ export class TwoFactorComponent implements OnInit, OnDestroy { return; } - this.cleanupU2f(); + this.cleanupWebAuthn(); this.title = (TwoFactorProviders as any)[this.selectedProviderType].name; const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType); switch (this.selectedProviderType) { - case TwoFactorProviderType.U2f: - if (!this.u2fSupported || this.u2f == null) { + case TwoFactorProviderType.WebAuthn: + if (!this.webAuthnSupported || this.webAuthn == null) { break; } - if (providerData.Challenge != null) { - setTimeout(() => { - this.u2f.init(JSON.parse(providerData.Challenge)); - }, 500); - } else { - // TODO: Deprecated. Remove in future version. - const challenges = JSON.parse(providerData.Challenges); - if (challenges != null && challenges.length > 0) { - this.u2f.init({ - appId: challenges[0].appId, - challenge: challenges[0].challenge, - keys: challenges.map((c: any) => { - return { - version: c.version, - keyHandle: c.keyHandle, - }; - }), - }); - } - } + setTimeout(() => { + this.webAuthn.init(providerData); + }, 500); break; case TwoFactorProviderType.Duo: case TwoFactorProviderType.OrganizationDuo: @@ -177,9 +159,9 @@ export class TwoFactorComponent implements OnInit, OnDestroy { return; } - if (this.selectedProviderType === TwoFactorProviderType.U2f) { - if (this.u2f != null) { - this.u2f.stop(); + if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) { + if (this.webAuthn != null) { + this.webAuthn.stop(); } else { return; } @@ -189,33 +171,37 @@ export class TwoFactorComponent implements OnInit, OnDestroy { } try { - this.formPromise = this.authService.logInTwoFactor(this.selectedProviderType, this.token, this.remember); - const response: AuthResult = await this.formPromise; - const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); - await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); - if (this.onSuccessfulLogin != null) { - this.onSuccessfulLogin(); - } - this.platformUtilsService.eventTrack('Logged In From Two-step'); - if (response.resetMasterPassword) { - this.successRoute = 'set-password'; - } - if (this.onSuccessfulLoginNavigate != null) { - this.onSuccessfulLoginNavigate(); - } else { - this.router.navigate([this.successRoute], { - queryParams: { - identifier: this.identifier, - }, - }); - } + await this.doSubmit(); } catch { - if (this.selectedProviderType === TwoFactorProviderType.U2f && this.u2f != null) { - this.u2f.start(); + if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) { + this.webAuthn.start(); } } } + async doSubmit() { + this.formPromise = this.authService.logInTwoFactor(this.selectedProviderType, this.token, this.remember); + const response: AuthResult = await this.formPromise; + const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); + await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); + if (this.onSuccessfulLogin != null) { + this.onSuccessfulLogin(); + } + this.platformUtilsService.eventTrack('Logged In From Two-step'); + if (response.resetMasterPassword) { + this.successRoute = 'set-password'; + } + if (this.onSuccessfulLoginNavigate != null) { + this.onSuccessfulLoginNavigate(); + } else { + this.router.navigate([this.successRoute], { + queryParams: { + identifier: this.identifier, + }, + }); + } + } + async sendEmail(doToast: boolean) { if (this.selectedProviderType !== TwoFactorProviderType.Email) { return; @@ -238,10 +224,10 @@ export class TwoFactorComponent implements OnInit, OnDestroy { this.emailPromise = null; } - private cleanupU2f() { - if (this.u2f != null) { - this.u2f.stop(); - this.u2f.cleanup(); + private cleanupWebAuthn() { + if (this.webAuthn != null) { + this.webAuthn.stop(); + this.webAuthn.cleanup(); } } diff --git a/src/cli/services/cliPlatformUtils.service.ts b/src/cli/services/cliPlatformUtils.service.ts index 333b5c574a..bba2937633 100644 --- a/src/cli/services/cliPlatformUtils.service.ts +++ b/src/cli/services/cliPlatformUtils.service.ts @@ -100,7 +100,7 @@ export class CliPlatformUtilsService implements PlatformUtilsService { return this.packageJson.version; } - supportsU2f(win: Window) { + supportsWebAuthn(win: Window) { return false; } diff --git a/src/electron/services/electronPlatformUtils.service.ts b/src/electron/services/electronPlatformUtils.service.ts index 9d97e33854..f4c47afca1 100644 --- a/src/electron/services/electronPlatformUtils.service.ts +++ b/src/electron/services/electronPlatformUtils.service.ts @@ -130,10 +130,8 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { return remote.app.getVersion(); } - supportsU2f(win: Window): boolean { - // Not supported in Electron at this time. - // ref: https://github.com/electron/electron/issues/3226 - return false; + supportsWebAuthn(win: Window): boolean { + return true; } supportsDuo(): boolean { diff --git a/src/enums/twoFactorProviderType.ts b/src/enums/twoFactorProviderType.ts index f866295c3b..c0de3f4a20 100644 --- a/src/enums/twoFactorProviderType.ts +++ b/src/enums/twoFactorProviderType.ts @@ -6,4 +6,5 @@ export enum TwoFactorProviderType { U2f = 4, Remember = 5, OrganizationDuo = 6, + WebAuthn = 7, } diff --git a/src/misc/u2f.ts b/src/misc/webauthn.ts similarity index 56% rename from src/misc/u2f.ts rename to src/misc/webauthn.ts index 4e215fa51a..e6d2583343 100644 --- a/src/misc/u2f.ts +++ b/src/misc/webauthn.ts @@ -1,24 +1,37 @@ -export class U2f { +import { I18nService } from '../abstractions/i18n.service'; +import { PlatformUtilsService } from '../abstractions/platformUtils.service'; + +export class WebAuthn { private iframe: HTMLIFrameElement = null; private connectorLink: HTMLAnchorElement; private parseFunction = this.parseMessage.bind(this); - constructor(private win: Window, private webVaultUrl: string, private successCallback: Function, - private errorCallback: Function, private infoCallback: Function) { + constructor(private win: Window, private webVaultUrl: string, private webAuthnNewTab: boolean, + private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, + private successCallback: Function, private errorCallback: Function, private infoCallback: Function) { this.connectorLink = win.document.createElement('a'); - this.webVaultUrl = webVaultUrl != null && webVaultUrl !== '' ? webVaultUrl : 'https://vault.bitwarden.com'; } init(data: any): void { - this.connectorLink.href = this.webVaultUrl + '/u2f-connector.html' + - '?data=' + this.base64Encode(JSON.stringify(data)) + - '&parent=' + encodeURIComponent(this.win.document.location.href) + - '&v=1'; + const params = new URLSearchParams({ + data: this.base64Encode(JSON.stringify(data)), + parent: encodeURIComponent(this.win.document.location.href), + btnText: encodeURIComponent(this.i18nService.t('webAuthnAuthenticate')), + v: '1', + }); - this.iframe = this.win.document.getElementById('u2f_iframe') as HTMLIFrameElement; - this.iframe.src = this.connectorLink.href; + if (this.webAuthnNewTab) { + // Firefox fallback which opens the webauthn page in a new tab + params.append('locale', this.i18nService.translationLocale); + this.platformUtilsService.launchUri(`${this.webVaultUrl}/webauthn-fallback-connector.html?${params}`); + } else { + this.connectorLink.href = `${this.webVaultUrl}/webauthn-connector.html?${params}`; + this.iframe = this.win.document.getElementById('webauthn_iframe') as HTMLIFrameElement; + this.iframe.allow = 'publickey-credentials-get ' + new URL(this.webVaultUrl).origin; + this.iframe.src = this.connectorLink.href; - this.win.addEventListener('message', this.parseFunction, false); + this.win.addEventListener('message', this.parseFunction, false); + } } stop() { diff --git a/src/models/request/updateTwoFactorU2fRequest.ts b/src/models/request/updateTwoFactorU2fRequest.ts deleted file mode 100644 index 0b3fb7c2be..0000000000 --- a/src/models/request/updateTwoFactorU2fRequest.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { PasswordVerificationRequest } from './passwordVerificationRequest'; - -export class UpdateTwoFactorU2fRequest extends PasswordVerificationRequest { - deviceResponse: string; - name: string; - id: number; -} diff --git a/src/models/request/updateTwoFactorU2fDeleteRequest.ts b/src/models/request/updateTwoFactorWebAuthnDeleteRequest.ts similarity index 52% rename from src/models/request/updateTwoFactorU2fDeleteRequest.ts rename to src/models/request/updateTwoFactorWebAuthnDeleteRequest.ts index 01066a301e..943d9e414f 100644 --- a/src/models/request/updateTwoFactorU2fDeleteRequest.ts +++ b/src/models/request/updateTwoFactorWebAuthnDeleteRequest.ts @@ -1,5 +1,5 @@ import { PasswordVerificationRequest } from './passwordVerificationRequest'; -export class UpdateTwoFactorU2fDeleteRequest extends PasswordVerificationRequest { +export class UpdateTwoFactorWebAuthnDeleteRequest extends PasswordVerificationRequest { id: number; } diff --git a/src/models/request/updateTwoFactorWebAuthnRequest.ts b/src/models/request/updateTwoFactorWebAuthnRequest.ts new file mode 100644 index 0000000000..ef9308eb5c --- /dev/null +++ b/src/models/request/updateTwoFactorWebAuthnRequest.ts @@ -0,0 +1,7 @@ +import { PasswordVerificationRequest } from './passwordVerificationRequest'; + +export class UpdateTwoFactorWebAuthnRequest extends PasswordVerificationRequest { + deviceResponse: PublicKeyCredential; + name: string; + id: number; +} diff --git a/src/models/response/twoFactorU2fResponse.ts b/src/models/response/twoFactorU2fResponse.ts deleted file mode 100644 index 059cea312b..0000000000 --- a/src/models/response/twoFactorU2fResponse.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { BaseResponse } from './baseResponse'; - -export class TwoFactorU2fResponse extends BaseResponse { - enabled: boolean; - keys: KeyResponse[]; - - constructor(response: any) { - super(response); - this.enabled = this.getResponseProperty('Enabled'); - const keys = this.getResponseProperty('Keys'); - this.keys = keys == null ? null : keys.map((k: any) => new KeyResponse(k)); - } -} - -export class KeyResponse extends BaseResponse { - name: string; - id: number; - compromised: boolean; - - constructor(response: any) { - super(response); - this.name = this.getResponseProperty('Name'); - this.id = this.getResponseProperty('Id'); - this.compromised = this.getResponseProperty('Compromised'); - } -} - -export class ChallengeResponse extends BaseResponse { - userId: string; - appId: string; - challenge: string; - version: string; - - constructor(response: any) { - super(response); - this.userId = this.getResponseProperty('UserId'); - this.appId = this.getResponseProperty('AppId'); - this.challenge = this.getResponseProperty('Challenge'); - this.version = this.getResponseProperty('Version'); - } -} diff --git a/src/models/response/twoFactorWebAuthnResponse.ts b/src/models/response/twoFactorWebAuthnResponse.ts new file mode 100644 index 0000000000..499bdca534 --- /dev/null +++ b/src/models/response/twoFactorWebAuthnResponse.ts @@ -0,0 +1,59 @@ +import { Utils } from '../../misc/utils'; +import { BaseResponse } from './baseResponse'; + +export class TwoFactorWebAuthnResponse extends BaseResponse { + enabled: boolean; + keys: KeyResponse[]; + + constructor(response: any) { + super(response); + this.enabled = this.getResponseProperty('Enabled'); + const keys = this.getResponseProperty('Keys'); + this.keys = keys == null ? null : keys.map((k: any) => new KeyResponse(k)); + } +} + +export class KeyResponse extends BaseResponse { + name: string; + id: number; + migrated: boolean; + + constructor(response: any) { + super(response); + this.name = this.getResponseProperty('Name'); + this.id = this.getResponseProperty('Id'); + this.migrated = this.getResponseProperty('Migrated'); + } +} + +export class ChallengeResponse extends BaseResponse implements PublicKeyCredentialCreationOptions { + attestation?: AttestationConveyancePreference; + authenticatorSelection?: AuthenticatorSelectionCriteria; + challenge: BufferSource; + excludeCredentials?: PublicKeyCredentialDescriptor[]; + extensions?: AuthenticationExtensionsClientInputs; + pubKeyCredParams: PublicKeyCredentialParameters[]; + rp: PublicKeyCredentialRpEntity; + timeout?: number; + user: PublicKeyCredentialUserEntity; + + constructor(response: any) { + super(response); + this.attestation = this.getResponseProperty('attestation'); + this.authenticatorSelection = this.getResponseProperty('authenticatorSelection'); + this.challenge = Utils.fromUrlB64ToArray(this.getResponseProperty('challenge')); + this.excludeCredentials = this.getResponseProperty('excludeCredentials').map((c: any) => { + c.id = Utils.fromUrlB64ToArray(c.id).buffer; + return c; + }); + this.extensions = this.getResponseProperty('extensions'); + this.pubKeyCredParams = this.getResponseProperty('pubKeyCredParams'); + this.rp = this.getResponseProperty('rp'); + this.timeout = this.getResponseProperty('timeout'); + + const user = this.getResponseProperty('user'); + user.id = Utils.fromUrlB64ToArray(user.id); + + this.user = user; + } +} diff --git a/src/services/api.service.ts b/src/services/api.service.ts index 5f3aefe35b..9712c058d1 100644 --- a/src/services/api.service.ts +++ b/src/services/api.service.ts @@ -66,8 +66,8 @@ import { UpdateProfileRequest } from '../models/request/updateProfileRequest'; import { UpdateTwoFactorAuthenticatorRequest } from '../models/request/updateTwoFactorAuthenticatorRequest'; import { UpdateTwoFactorDuoRequest } from '../models/request/updateTwoFactorDuoRequest'; import { UpdateTwoFactorEmailRequest } from '../models/request/updateTwoFactorEmailRequest'; -import { UpdateTwoFactorU2fDeleteRequest } from '../models/request/updateTwoFactorU2fDeleteRequest'; -import { UpdateTwoFactorU2fRequest } from '../models/request/updateTwoFactorU2fRequest'; +import { UpdateTwoFactorWebAuthnDeleteRequest } from '../models/request/updateTwoFactorWebAuthnDeleteRequest'; +import { UpdateTwoFactorWebAuthnRequest } from '../models/request/updateTwoFactorWebAuthnRequest'; import { UpdateTwoFactorYubioOtpRequest } from '../models/request/updateTwoFactorYubioOtpRequest'; import { VerifyBankRequest } from '../models/request/verifyBankRequest'; import { VerifyDeleteRecoverRequest } from '../models/request/verifyDeleteRecoverRequest'; @@ -123,10 +123,8 @@ import { TwoFactorDuoResponse } from '../models/response/twoFactorDuoResponse'; import { TwoFactorEmailResponse } from '../models/response/twoFactorEmailResponse'; import { TwoFactorProviderResponse } from '../models/response/twoFactorProviderResponse'; import { TwoFactorRecoverResponse } from '../models/response/twoFactorRescoverResponse'; -import { - ChallengeResponse, - TwoFactorU2fResponse, -} from '../models/response/twoFactorU2fResponse'; +import { TwoFactorWebAuthnResponse } from '../models/response/twoFactorWebAuthnResponse'; +import { ChallengeResponse } from '../models/response/twoFactorWebAuthnResponse'; import { TwoFactorYubiKeyResponse } from '../models/response/twoFactorYubiKeyResponse'; import { UserKeyResponse } from '../models/response/userKeyResponse'; @@ -849,13 +847,13 @@ export class ApiService implements ApiServiceAbstraction { return new TwoFactorYubiKeyResponse(r); } - async getTwoFactorU2f(request: PasswordVerificationRequest): Promise { - const r = await this.send('POST', '/two-factor/get-u2f', request, true, true); - return new TwoFactorU2fResponse(r); + async getTwoFactorWebAuthn(request: PasswordVerificationRequest): Promise { + const r = await this.send('POST', '/two-factor/get-webauthn', request, true, true); + return new TwoFactorWebAuthnResponse(r); } - async getTwoFactorU2fChallenge(request: PasswordVerificationRequest): Promise { - const r = await this.send('POST', '/two-factor/get-u2f-challenge', request, true, true); + async getTwoFactorWebAuthnChallenge(request: PasswordVerificationRequest): Promise { + const r = await this.send('POST', '/two-factor/get-webauthn-challenge', request, true, true); return new ChallengeResponse(r); } @@ -891,14 +889,28 @@ export class ApiService implements ApiServiceAbstraction { return new TwoFactorYubiKeyResponse(r); } - async putTwoFactorU2f(request: UpdateTwoFactorU2fRequest): Promise { - const r = await this.send('PUT', '/two-factor/u2f', request, true, true); - return new TwoFactorU2fResponse(r); + async putTwoFactorWebAuthn(request: UpdateTwoFactorWebAuthnRequest): Promise { + const response = request.deviceResponse.response as AuthenticatorAttestationResponse; + const data: any = Object.assign({}, request); + + data.deviceResponse = { + id: request.deviceResponse.id, + rawId: btoa(request.deviceResponse.id), + type: request.deviceResponse.type, + extensions: request.deviceResponse.getClientExtensionResults(), + response: { + AttestationObject: Utils.fromBufferToB64(response.attestationObject), + clientDataJson: Utils.fromBufferToB64(response.clientDataJSON), + }, + }; + + const r = await this.send('PUT', '/two-factor/webauthn', data, true, true); + return new TwoFactorWebAuthnResponse(r); } - async deleteTwoFactorU2f(request: UpdateTwoFactorU2fDeleteRequest): Promise { - const r = await this.send('DELETE', '/two-factor/u2f', request, true, true); - return new TwoFactorU2fResponse(r); + async deleteTwoFactorWebAuthn(request: UpdateTwoFactorWebAuthnDeleteRequest): Promise { + const r = await this.send('DELETE', '/two-factor/webauthn', request, true, true); + return new TwoFactorWebAuthnResponse(r); } async putTwoFactorDisable(request: TwoFactorProviderRequest): Promise { diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 15013b1453..10048d0c69 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -57,14 +57,6 @@ export const TwoFactorProviders = { sort: 4, premium: false, }, - [TwoFactorProviderType.U2f]: { - type: TwoFactorProviderType.U2f, - name: null as string, - description: null as string, - priority: 4, - sort: 5, - premium: true, - }, [TwoFactorProviderType.Email]: { type: TwoFactorProviderType.Email, name: null as string, @@ -73,6 +65,14 @@ export const TwoFactorProviders = { sort: 6, premium: false, }, + [TwoFactorProviderType.WebAuthn]: { + type: TwoFactorProviderType.WebAuthn, + name: null as string, + description: null as string, + priority: 4, + sort: 5, + premium: true, + }, }; export class AuthService implements AuthServiceAbstraction { @@ -111,8 +111,8 @@ export class AuthService implements AuthServiceAbstraction { TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description = this.i18nService.t('duoOrganizationDesc'); - TwoFactorProviders[TwoFactorProviderType.U2f].name = this.i18nService.t('u2fTitle'); - TwoFactorProviders[TwoFactorProviderType.U2f].description = this.i18nService.t('u2fDesc'); + TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t('webAuthnTitle'); + TwoFactorProviders[TwoFactorProviderType.WebAuthn].description = this.i18nService.t('webAuthnDesc'); TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t('yubiKeyTitle'); TwoFactorProviders[TwoFactorProviderType.Yubikey].description = this.i18nService.t('yubiKeyDesc'); @@ -196,8 +196,8 @@ export class AuthService implements AuthServiceAbstraction { providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); } - if (this.twoFactorProvidersData.has(TwoFactorProviderType.U2f) && this.platformUtilsService.supportsU2f(win)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.U2f]); + if (this.twoFactorProvidersData.has(TwoFactorProviderType.WebAuthn) && this.platformUtilsService.supportsWebAuthn(win)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]); } if (this.twoFactorProvidersData.has(TwoFactorProviderType.Email)) { @@ -207,7 +207,7 @@ export class AuthService implements AuthServiceAbstraction { return providers; } - getDefaultTwoFactorProvider(u2fSupported: boolean): TwoFactorProviderType { + getDefaultTwoFactorProvider(webAuthnSupported: boolean): TwoFactorProviderType { if (this.twoFactorProvidersData == null) { return null; } @@ -222,7 +222,7 @@ export class AuthService implements AuthServiceAbstraction { this.twoFactorProvidersData.forEach((value, type) => { const provider = (TwoFactorProviders as any)[type]; if (provider != null && provider.priority > providerPriority) { - if (type === TwoFactorProviderType.U2f && !u2fSupported) { + if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { return; }