1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-27 12:36:14 +01:00
This commit is contained in:
Oscar Hinton 2021-03-15 16:16:51 +01:00 committed by GitHub
parent f80e89465f
commit f20af0cd7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 210 additions and 185 deletions

View File

@ -62,8 +62,8 @@ import { UpdateProfileRequest } from '../models/request/updateProfileRequest';
import { UpdateTwoFactorAuthenticatorRequest } from '../models/request/updateTwoFactorAuthenticatorRequest'; import { UpdateTwoFactorAuthenticatorRequest } from '../models/request/updateTwoFactorAuthenticatorRequest';
import { UpdateTwoFactorDuoRequest } from '../models/request/updateTwoFactorDuoRequest'; import { UpdateTwoFactorDuoRequest } from '../models/request/updateTwoFactorDuoRequest';
import { UpdateTwoFactorEmailRequest } from '../models/request/updateTwoFactorEmailRequest'; import { UpdateTwoFactorEmailRequest } from '../models/request/updateTwoFactorEmailRequest';
import { UpdateTwoFactorU2fDeleteRequest } from '../models/request/updateTwoFactorU2fDeleteRequest'; import { UpdateTwoFactorWebAuthnDeleteRequest } from '../models/request/updateTwoFactorWebAuthnDeleteRequest';
import { UpdateTwoFactorU2fRequest } from '../models/request/updateTwoFactorU2fRequest'; import { UpdateTwoFactorWebAuthnRequest } from '../models/request/updateTwoFactorWebAuthnRequest';
import { UpdateTwoFactorYubioOtpRequest } from '../models/request/updateTwoFactorYubioOtpRequest'; import { UpdateTwoFactorYubioOtpRequest } from '../models/request/updateTwoFactorYubioOtpRequest';
import { VerifyBankRequest } from '../models/request/verifyBankRequest'; import { VerifyBankRequest } from '../models/request/verifyBankRequest';
import { VerifyDeleteRecoverRequest } from '../models/request/verifyDeleteRecoverRequest'; import { VerifyDeleteRecoverRequest } from '../models/request/verifyDeleteRecoverRequest';
@ -117,10 +117,7 @@ import { TwoFactorDuoResponse } from '../models/response/twoFactorDuoResponse';
import { TwoFactorEmailResponse } from '../models/response/twoFactorEmailResponse'; import { TwoFactorEmailResponse } from '../models/response/twoFactorEmailResponse';
import { TwoFactorProviderResponse } from '../models/response/twoFactorProviderResponse'; import { TwoFactorProviderResponse } from '../models/response/twoFactorProviderResponse';
import { TwoFactorRecoverResponse } from '../models/response/twoFactorRescoverResponse'; import { TwoFactorRecoverResponse } from '../models/response/twoFactorRescoverResponse';
import { import { ChallengeResponse, TwoFactorWebAuthnResponse } from '../models/response/twoFactorWebAuthnResponse';
ChallengeResponse,
TwoFactorU2fResponse,
} from '../models/response/twoFactorU2fResponse';
import { TwoFactorYubiKeyResponse } from '../models/response/twoFactorYubiKeyResponse'; import { TwoFactorYubiKeyResponse } from '../models/response/twoFactorYubiKeyResponse';
import { UserKeyResponse } from '../models/response/userKeyResponse'; import { UserKeyResponse } from '../models/response/userKeyResponse';
@ -274,8 +271,8 @@ export abstract class ApiService {
getTwoFactorOrganizationDuo: (organizationId: string, getTwoFactorOrganizationDuo: (organizationId: string,
request: PasswordVerificationRequest) => Promise<TwoFactorDuoResponse>; request: PasswordVerificationRequest) => Promise<TwoFactorDuoResponse>;
getTwoFactorYubiKey: (request: PasswordVerificationRequest) => Promise<TwoFactorYubiKeyResponse>; getTwoFactorYubiKey: (request: PasswordVerificationRequest) => Promise<TwoFactorYubiKeyResponse>;
getTwoFactorU2f: (request: PasswordVerificationRequest) => Promise<TwoFactorU2fResponse>; getTwoFactorWebAuthn: (request: PasswordVerificationRequest) => Promise<TwoFactorWebAuthnResponse>;
getTwoFactorU2fChallenge: (request: PasswordVerificationRequest) => Promise<ChallengeResponse>; getTwoFactorWebAuthnChallenge: (request: PasswordVerificationRequest) => Promise<ChallengeResponse>;
getTwoFactorRecover: (request: PasswordVerificationRequest) => Promise<TwoFactorRecoverResponse>; getTwoFactorRecover: (request: PasswordVerificationRequest) => Promise<TwoFactorRecoverResponse>;
putTwoFactorAuthenticator: ( putTwoFactorAuthenticator: (
request: UpdateTwoFactorAuthenticatorRequest) => Promise<TwoFactorAuthenticatorResponse>; request: UpdateTwoFactorAuthenticatorRequest) => Promise<TwoFactorAuthenticatorResponse>;
@ -284,8 +281,8 @@ export abstract class ApiService {
putTwoFactorOrganizationDuo: (organizationId: string, putTwoFactorOrganizationDuo: (organizationId: string,
request: UpdateTwoFactorDuoRequest) => Promise<TwoFactorDuoResponse>; request: UpdateTwoFactorDuoRequest) => Promise<TwoFactorDuoResponse>;
putTwoFactorYubiKey: (request: UpdateTwoFactorYubioOtpRequest) => Promise<TwoFactorYubiKeyResponse>; putTwoFactorYubiKey: (request: UpdateTwoFactorYubioOtpRequest) => Promise<TwoFactorYubiKeyResponse>;
putTwoFactorU2f: (request: UpdateTwoFactorU2fRequest) => Promise<TwoFactorU2fResponse>; putTwoFactorWebAuthn: (request: UpdateTwoFactorWebAuthnRequest) => Promise<TwoFactorWebAuthnResponse>;
deleteTwoFactorU2f: (request: UpdateTwoFactorU2fDeleteRequest) => Promise<TwoFactorU2fResponse>; deleteTwoFactorWebAuthn: (request: UpdateTwoFactorWebAuthnDeleteRequest) => Promise<TwoFactorWebAuthnResponse>;
putTwoFactorDisable: (request: TwoFactorProviderRequest) => Promise<TwoFactorProviderResponse>; putTwoFactorDisable: (request: TwoFactorProviderRequest) => Promise<TwoFactorProviderResponse>;
putTwoFactorOrganizationDisable: (organizationId: string, putTwoFactorOrganizationDisable: (organizationId: string,
request: TwoFactorProviderRequest) => Promise<TwoFactorProviderResponse>; request: TwoFactorProviderRequest) => Promise<TwoFactorProviderResponse>;

View File

@ -27,7 +27,7 @@ export abstract class AuthService {
twoFactorToken: string, remember?: boolean) => Promise<AuthResult>; twoFactorToken: string, remember?: boolean) => Promise<AuthResult>;
logOut: (callback: Function) => void; logOut: (callback: Function) => void;
getSupportedTwoFactorProviders: (win: Window) => any[]; getSupportedTwoFactorProviders: (win: Window) => any[];
getDefaultTwoFactorProvider: (u2fSupported: boolean) => TwoFactorProviderType; getDefaultTwoFactorProvider: (webAuthnSupported: boolean) => TwoFactorProviderType;
makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>; makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>;
authingWithApiKey: () => boolean; authingWithApiKey: () => boolean;
authingWithSso: () => boolean; authingWithSso: () => boolean;

View File

@ -21,7 +21,7 @@ export abstract class PlatformUtilsService {
launchUri: (uri: string, options?: any) => void; launchUri: (uri: string, options?: any) => void;
saveFile: (win: Window, blobData: any, blobOptions: any, fileName: string) => void; saveFile: (win: Window, blobData: any, blobOptions: any, fileName: string) => void;
getApplicationVersion: () => string; getApplicationVersion: () => string;
supportsU2f: (win: Window) => boolean; supportsWebAuthn: (win: Window) => boolean;
supportsDuo: () => boolean; supportsDuo: () => boolean;
showToast: (type: 'error' | 'success' | 'warning' | 'info', title: string, text: string | string[], showToast: (type: 'error' | 'success' | 'warning' | 'info', title: string, text: string | string[],
options?: any) => void; options?: any) => void;

View File

@ -26,18 +26,18 @@ import { TwoFactorProviders } from '../../services/auth.service';
import { ConstantsService } from '../../services/constants.service'; import { ConstantsService } from '../../services/constants.service';
import * as DuoWebSDK from 'duo_web_sdk'; import * as DuoWebSDK from 'duo_web_sdk';
import { U2f } from '../../misc/u2f'; import { WebAuthn } from '../../misc/webauthn';
export class TwoFactorComponent implements OnInit, OnDestroy { export class TwoFactorComponent implements OnInit, OnDestroy {
token: string = ''; token: string = '';
remember: boolean = false; remember: boolean = false;
u2fReady: boolean = false; webAuthnReady: boolean = false;
initU2f: boolean = true; webAuthnNewTab: boolean = false;
providers = TwoFactorProviders; providers = TwoFactorProviders;
providerType = TwoFactorProviderType; providerType = TwoFactorProviderType;
selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator; selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
u2fSupported: boolean = false; webAuthnSupported: boolean = false;
u2f: U2f = null; webAuthn: WebAuthn = null;
title: string = ''; title: string = '';
twoFactorEmail: string = null; twoFactorEmail: string = null;
formPromise: Promise<any>; formPromise: Promise<any>;
@ -54,7 +54,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
protected platformUtilsService: PlatformUtilsService, protected win: Window, protected platformUtilsService: PlatformUtilsService, protected win: Window,
protected environmentService: EnvironmentService, protected stateService: StateService, protected environmentService: EnvironmentService, protected stateService: StateService,
protected storageService: StorageService, protected route: ActivatedRoute) { protected storageService: StorageService, protected route: ActivatedRoute) {
this.u2fSupported = this.platformUtilsService.supportsU2f(win); this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
} }
async ngOnInit() { async ngOnInit() {
@ -77,33 +77,32 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
this.successRoute = 'lock'; this.successRoute = 'lock';
} }
if (this.initU2f && this.win != null && this.u2fSupported) { if (this.win != null && this.webAuthnSupported) {
let customWebVaultUrl: string = null; let webVaultUrl = this.environmentService.getWebVaultUrl();
if (this.environmentService.baseUrl != null) { if (webVaultUrl == null) {
customWebVaultUrl = this.environmentService.baseUrl; webVaultUrl = 'https://vault.bitwarden.com';
} else if (this.environmentService.webVaultUrl != null) {
customWebVaultUrl = this.environmentService.webVaultUrl;
} }
this.webAuthn = new WebAuthn(this.win, webVaultUrl, this.webAuthnNewTab, this.platformUtilsService,
this.u2f = new U2f(this.win, customWebVaultUrl, (token: string) => { this.i18nService, (token: string) => {
this.token = token; this.token = token;
this.submit(); this.submit();
}, (error: string) => { }, (error: string) => {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error); this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error);
}, (info: string) => { }, (info: string) => {
if (info === 'ready') { if (info === 'ready') {
this.u2fReady = true; this.webAuthnReady = true;
}
} }
}); );
} }
this.selectedProviderType = this.authService.getDefaultTwoFactorProvider(this.u2fSupported); this.selectedProviderType = this.authService.getDefaultTwoFactorProvider(this.webAuthnSupported);
await this.init(); await this.init();
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.cleanupU2f(); this.cleanupWebAuthn();
this.u2f = null; this.webAuthn = null;
} }
async init() { async init() {
@ -112,35 +111,18 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
return; return;
} }
this.cleanupU2f(); this.cleanupWebAuthn();
this.title = (TwoFactorProviders as any)[this.selectedProviderType].name; this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType); const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType);
switch (this.selectedProviderType) { switch (this.selectedProviderType) {
case TwoFactorProviderType.U2f: case TwoFactorProviderType.WebAuthn:
if (!this.u2fSupported || this.u2f == null) { if (!this.webAuthnSupported || this.webAuthn == null) {
break; break;
} }
if (providerData.Challenge != null) { setTimeout(() => {
setTimeout(() => { this.webAuthn.init(providerData);
this.u2f.init(JSON.parse(providerData.Challenge)); }, 500);
}, 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,
};
}),
});
}
}
break; break;
case TwoFactorProviderType.Duo: case TwoFactorProviderType.Duo:
case TwoFactorProviderType.OrganizationDuo: case TwoFactorProviderType.OrganizationDuo:
@ -177,9 +159,9 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
return; return;
} }
if (this.selectedProviderType === TwoFactorProviderType.U2f) { if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) {
if (this.u2f != null) { if (this.webAuthn != null) {
this.u2f.stop(); this.webAuthn.stop();
} else { } else {
return; return;
} }
@ -189,33 +171,37 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
} }
try { try {
this.formPromise = this.authService.logInTwoFactor(this.selectedProviderType, this.token, this.remember); await this.doSubmit();
const response: AuthResult = await this.formPromise;
const disableFavicon = await this.storageService.get<boolean>(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,
},
});
}
} catch { } catch {
if (this.selectedProviderType === TwoFactorProviderType.U2f && this.u2f != null) { if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) {
this.u2f.start(); 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<boolean>(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) { async sendEmail(doToast: boolean) {
if (this.selectedProviderType !== TwoFactorProviderType.Email) { if (this.selectedProviderType !== TwoFactorProviderType.Email) {
return; return;
@ -238,10 +224,10 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
this.emailPromise = null; this.emailPromise = null;
} }
private cleanupU2f() { private cleanupWebAuthn() {
if (this.u2f != null) { if (this.webAuthn != null) {
this.u2f.stop(); this.webAuthn.stop();
this.u2f.cleanup(); this.webAuthn.cleanup();
} }
} }

View File

@ -100,7 +100,7 @@ export class CliPlatformUtilsService implements PlatformUtilsService {
return this.packageJson.version; return this.packageJson.version;
} }
supportsU2f(win: Window) { supportsWebAuthn(win: Window) {
return false; return false;
} }

View File

@ -130,10 +130,8 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
return remote.app.getVersion(); return remote.app.getVersion();
} }
supportsU2f(win: Window): boolean { supportsWebAuthn(win: Window): boolean {
// Not supported in Electron at this time. return true;
// ref: https://github.com/electron/electron/issues/3226
return false;
} }
supportsDuo(): boolean { supportsDuo(): boolean {

View File

@ -6,4 +6,5 @@ export enum TwoFactorProviderType {
U2f = 4, U2f = 4,
Remember = 5, Remember = 5,
OrganizationDuo = 6, OrganizationDuo = 6,
WebAuthn = 7,
} }

View File

@ -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 iframe: HTMLIFrameElement = null;
private connectorLink: HTMLAnchorElement; private connectorLink: HTMLAnchorElement;
private parseFunction = this.parseMessage.bind(this); private parseFunction = this.parseMessage.bind(this);
constructor(private win: Window, private webVaultUrl: string, private successCallback: Function, constructor(private win: Window, private webVaultUrl: string, private webAuthnNewTab: boolean,
private errorCallback: Function, private infoCallback: Function) { private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private successCallback: Function, private errorCallback: Function, private infoCallback: Function) {
this.connectorLink = win.document.createElement('a'); this.connectorLink = win.document.createElement('a');
this.webVaultUrl = webVaultUrl != null && webVaultUrl !== '' ? webVaultUrl : 'https://vault.bitwarden.com';
} }
init(data: any): void { init(data: any): void {
this.connectorLink.href = this.webVaultUrl + '/u2f-connector.html' + const params = new URLSearchParams({
'?data=' + this.base64Encode(JSON.stringify(data)) + data: this.base64Encode(JSON.stringify(data)),
'&parent=' + encodeURIComponent(this.win.document.location.href) + parent: encodeURIComponent(this.win.document.location.href),
'&v=1'; btnText: encodeURIComponent(this.i18nService.t('webAuthnAuthenticate')),
v: '1',
});
this.iframe = this.win.document.getElementById('u2f_iframe') as HTMLIFrameElement; if (this.webAuthnNewTab) {
this.iframe.src = this.connectorLink.href; // 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() { stop() {

View File

@ -1,7 +0,0 @@
import { PasswordVerificationRequest } from './passwordVerificationRequest';
export class UpdateTwoFactorU2fRequest extends PasswordVerificationRequest {
deviceResponse: string;
name: string;
id: number;
}

View File

@ -1,5 +1,5 @@
import { PasswordVerificationRequest } from './passwordVerificationRequest'; import { PasswordVerificationRequest } from './passwordVerificationRequest';
export class UpdateTwoFactorU2fDeleteRequest extends PasswordVerificationRequest { export class UpdateTwoFactorWebAuthnDeleteRequest extends PasswordVerificationRequest {
id: number; id: number;
} }

View File

@ -0,0 +1,7 @@
import { PasswordVerificationRequest } from './passwordVerificationRequest';
export class UpdateTwoFactorWebAuthnRequest extends PasswordVerificationRequest {
deviceResponse: PublicKeyCredential;
name: string;
id: number;
}

View File

@ -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');
}
}

View File

@ -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;
}
}

View File

@ -66,8 +66,8 @@ import { UpdateProfileRequest } from '../models/request/updateProfileRequest';
import { UpdateTwoFactorAuthenticatorRequest } from '../models/request/updateTwoFactorAuthenticatorRequest'; import { UpdateTwoFactorAuthenticatorRequest } from '../models/request/updateTwoFactorAuthenticatorRequest';
import { UpdateTwoFactorDuoRequest } from '../models/request/updateTwoFactorDuoRequest'; import { UpdateTwoFactorDuoRequest } from '../models/request/updateTwoFactorDuoRequest';
import { UpdateTwoFactorEmailRequest } from '../models/request/updateTwoFactorEmailRequest'; import { UpdateTwoFactorEmailRequest } from '../models/request/updateTwoFactorEmailRequest';
import { UpdateTwoFactorU2fDeleteRequest } from '../models/request/updateTwoFactorU2fDeleteRequest'; import { UpdateTwoFactorWebAuthnDeleteRequest } from '../models/request/updateTwoFactorWebAuthnDeleteRequest';
import { UpdateTwoFactorU2fRequest } from '../models/request/updateTwoFactorU2fRequest'; import { UpdateTwoFactorWebAuthnRequest } from '../models/request/updateTwoFactorWebAuthnRequest';
import { UpdateTwoFactorYubioOtpRequest } from '../models/request/updateTwoFactorYubioOtpRequest'; import { UpdateTwoFactorYubioOtpRequest } from '../models/request/updateTwoFactorYubioOtpRequest';
import { VerifyBankRequest } from '../models/request/verifyBankRequest'; import { VerifyBankRequest } from '../models/request/verifyBankRequest';
import { VerifyDeleteRecoverRequest } from '../models/request/verifyDeleteRecoverRequest'; import { VerifyDeleteRecoverRequest } from '../models/request/verifyDeleteRecoverRequest';
@ -123,10 +123,8 @@ import { TwoFactorDuoResponse } from '../models/response/twoFactorDuoResponse';
import { TwoFactorEmailResponse } from '../models/response/twoFactorEmailResponse'; import { TwoFactorEmailResponse } from '../models/response/twoFactorEmailResponse';
import { TwoFactorProviderResponse } from '../models/response/twoFactorProviderResponse'; import { TwoFactorProviderResponse } from '../models/response/twoFactorProviderResponse';
import { TwoFactorRecoverResponse } from '../models/response/twoFactorRescoverResponse'; import { TwoFactorRecoverResponse } from '../models/response/twoFactorRescoverResponse';
import { import { TwoFactorWebAuthnResponse } from '../models/response/twoFactorWebAuthnResponse';
ChallengeResponse, import { ChallengeResponse } from '../models/response/twoFactorWebAuthnResponse';
TwoFactorU2fResponse,
} from '../models/response/twoFactorU2fResponse';
import { TwoFactorYubiKeyResponse } from '../models/response/twoFactorYubiKeyResponse'; import { TwoFactorYubiKeyResponse } from '../models/response/twoFactorYubiKeyResponse';
import { UserKeyResponse } from '../models/response/userKeyResponse'; import { UserKeyResponse } from '../models/response/userKeyResponse';
@ -849,13 +847,13 @@ export class ApiService implements ApiServiceAbstraction {
return new TwoFactorYubiKeyResponse(r); return new TwoFactorYubiKeyResponse(r);
} }
async getTwoFactorU2f(request: PasswordVerificationRequest): Promise<TwoFactorU2fResponse> { async getTwoFactorWebAuthn(request: PasswordVerificationRequest): Promise<TwoFactorWebAuthnResponse> {
const r = await this.send('POST', '/two-factor/get-u2f', request, true, true); const r = await this.send('POST', '/two-factor/get-webauthn', request, true, true);
return new TwoFactorU2fResponse(r); return new TwoFactorWebAuthnResponse(r);
} }
async getTwoFactorU2fChallenge(request: PasswordVerificationRequest): Promise<ChallengeResponse> { async getTwoFactorWebAuthnChallenge(request: PasswordVerificationRequest): Promise<ChallengeResponse> {
const r = await this.send('POST', '/two-factor/get-u2f-challenge', request, true, true); const r = await this.send('POST', '/two-factor/get-webauthn-challenge', request, true, true);
return new ChallengeResponse(r); return new ChallengeResponse(r);
} }
@ -891,14 +889,28 @@ export class ApiService implements ApiServiceAbstraction {
return new TwoFactorYubiKeyResponse(r); return new TwoFactorYubiKeyResponse(r);
} }
async putTwoFactorU2f(request: UpdateTwoFactorU2fRequest): Promise<TwoFactorU2fResponse> { async putTwoFactorWebAuthn(request: UpdateTwoFactorWebAuthnRequest): Promise<TwoFactorWebAuthnResponse> {
const r = await this.send('PUT', '/two-factor/u2f', request, true, true); const response = request.deviceResponse.response as AuthenticatorAttestationResponse;
return new TwoFactorU2fResponse(r); 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<TwoFactorU2fResponse> { async deleteTwoFactorWebAuthn(request: UpdateTwoFactorWebAuthnDeleteRequest): Promise<TwoFactorWebAuthnResponse> {
const r = await this.send('DELETE', '/two-factor/u2f', request, true, true); const r = await this.send('DELETE', '/two-factor/webauthn', request, true, true);
return new TwoFactorU2fResponse(r); return new TwoFactorWebAuthnResponse(r);
} }
async putTwoFactorDisable(request: TwoFactorProviderRequest): Promise<TwoFactorProviderResponse> { async putTwoFactorDisable(request: TwoFactorProviderRequest): Promise<TwoFactorProviderResponse> {

View File

@ -57,14 +57,6 @@ export const TwoFactorProviders = {
sort: 4, sort: 4,
premium: false, premium: false,
}, },
[TwoFactorProviderType.U2f]: {
type: TwoFactorProviderType.U2f,
name: null as string,
description: null as string,
priority: 4,
sort: 5,
premium: true,
},
[TwoFactorProviderType.Email]: { [TwoFactorProviderType.Email]: {
type: TwoFactorProviderType.Email, type: TwoFactorProviderType.Email,
name: null as string, name: null as string,
@ -73,6 +65,14 @@ export const TwoFactorProviders = {
sort: 6, sort: 6,
premium: false, 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 { export class AuthService implements AuthServiceAbstraction {
@ -111,8 +111,8 @@ export class AuthService implements AuthServiceAbstraction {
TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description = TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description =
this.i18nService.t('duoOrganizationDesc'); this.i18nService.t('duoOrganizationDesc');
TwoFactorProviders[TwoFactorProviderType.U2f].name = this.i18nService.t('u2fTitle'); TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t('webAuthnTitle');
TwoFactorProviders[TwoFactorProviderType.U2f].description = this.i18nService.t('u2fDesc'); TwoFactorProviders[TwoFactorProviderType.WebAuthn].description = this.i18nService.t('webAuthnDesc');
TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t('yubiKeyTitle'); TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t('yubiKeyTitle');
TwoFactorProviders[TwoFactorProviderType.Yubikey].description = this.i18nService.t('yubiKeyDesc'); TwoFactorProviders[TwoFactorProviderType.Yubikey].description = this.i18nService.t('yubiKeyDesc');
@ -196,8 +196,8 @@ export class AuthService implements AuthServiceAbstraction {
providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]);
} }
if (this.twoFactorProvidersData.has(TwoFactorProviderType.U2f) && this.platformUtilsService.supportsU2f(win)) { if (this.twoFactorProvidersData.has(TwoFactorProviderType.WebAuthn) && this.platformUtilsService.supportsWebAuthn(win)) {
providers.push(TwoFactorProviders[TwoFactorProviderType.U2f]); providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]);
} }
if (this.twoFactorProvidersData.has(TwoFactorProviderType.Email)) { if (this.twoFactorProvidersData.has(TwoFactorProviderType.Email)) {
@ -207,7 +207,7 @@ export class AuthService implements AuthServiceAbstraction {
return providers; return providers;
} }
getDefaultTwoFactorProvider(u2fSupported: boolean): TwoFactorProviderType { getDefaultTwoFactorProvider(webAuthnSupported: boolean): TwoFactorProviderType {
if (this.twoFactorProvidersData == null) { if (this.twoFactorProvidersData == null) {
return null; return null;
} }
@ -222,7 +222,7 @@ export class AuthService implements AuthServiceAbstraction {
this.twoFactorProvidersData.forEach((value, type) => { this.twoFactorProvidersData.forEach((value, type) => {
const provider = (TwoFactorProviders as any)[type]; const provider = (TwoFactorProviders as any)[type];
if (provider != null && provider.priority > providerPriority) { if (provider != null && provider.priority > providerPriority) {
if (type === TwoFactorProviderType.U2f && !u2fSupported) { if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) {
return; return;
} }