mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-27 12:36:14 +01:00
WebAuthn (#163)
This commit is contained in:
parent
f80e89465f
commit
f20af0cd7c
@ -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>;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -6,4 +6,5 @@ export enum TwoFactorProviderType {
|
|||||||
U2f = 4,
|
U2f = 4,
|
||||||
Remember = 5,
|
Remember = 5,
|
||||||
OrganizationDuo = 6,
|
OrganizationDuo = 6,
|
||||||
|
WebAuthn = 7,
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
@ -1,7 +0,0 @@
|
|||||||
import { PasswordVerificationRequest } from './passwordVerificationRequest';
|
|
||||||
|
|
||||||
export class UpdateTwoFactorU2fRequest extends PasswordVerificationRequest {
|
|
||||||
deviceResponse: string;
|
|
||||||
name: string;
|
|
||||||
id: number;
|
|
||||||
}
|
|
@ -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;
|
||||||
}
|
}
|
7
src/models/request/updateTwoFactorWebAuthnRequest.ts
Normal file
7
src/models/request/updateTwoFactorWebAuthnRequest.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { PasswordVerificationRequest } from './passwordVerificationRequest';
|
||||||
|
|
||||||
|
export class UpdateTwoFactorWebAuthnRequest extends PasswordVerificationRequest {
|
||||||
|
deviceResponse: PublicKeyCredential;
|
||||||
|
name: string;
|
||||||
|
id: number;
|
||||||
|
}
|
@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
59
src/models/response/twoFactorWebAuthnResponse.ts
Normal file
59
src/models/response/twoFactorWebAuthnResponse.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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> {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user