Add shared webauthn component (#9771)
This commit is contained in:
parent
96538a68fd
commit
69a37a884f
|
@ -611,6 +611,9 @@
|
|||
"verificationCodeRequired": {
|
||||
"message": "Verification code is required."
|
||||
},
|
||||
"webauthnCancelOrTimeout": {
|
||||
"message": "The authentication was cancelled or took too long. Please try again."
|
||||
},
|
||||
"invalidVerificationCode": {
|
||||
"message": "Invalid verification code"
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@ import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
|
|||
import { ActivatedRoute, Router, RouterLink } from "@angular/router";
|
||||
|
||||
import { TwoFactorAuthAuthenticatorComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
|
||||
import { TwoFactorAuthWebAuthnComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-webauthn.component";
|
||||
import { TwoFactorAuthYubikeyComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-yubikey.component";
|
||||
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth.component";
|
||||
import { TwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-options.component";
|
||||
|
@ -64,6 +65,7 @@ import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component";
|
|||
TwoFactorAuthEmailComponent,
|
||||
TwoFactorAuthAuthenticatorComponent,
|
||||
TwoFactorAuthYubikeyComponent,
|
||||
TwoFactorAuthWebAuthnComponent,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import { RouterLink } from "@angular/router";
|
|||
|
||||
import { TwoFactorAuthAuthenticatorComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
|
||||
import { TwoFactorAuthEmailComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component";
|
||||
import { TwoFactorAuthWebAuthnComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component";
|
||||
import { TwoFactorAuthYubikeyComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component";
|
||||
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component";
|
||||
import { TwoFactorOptionsComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component";
|
||||
|
@ -39,6 +40,7 @@ import { TypographyModule } from "../../../../libs/components/src/typography";
|
|||
TwoFactorAuthEmailComponent,
|
||||
TwoFactorAuthAuthenticatorComponent,
|
||||
TwoFactorAuthYubikeyComponent,
|
||||
TwoFactorAuthWebAuthnComponent,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
})
|
||||
|
|
|
@ -627,6 +627,9 @@
|
|||
"verificationCodeRequired": {
|
||||
"message": "Verification code is required."
|
||||
},
|
||||
"webauthnCancelOrTimeout": {
|
||||
"message": "The authentication was cancelled or took too long. Please try again."
|
||||
},
|
||||
"invalidVerificationCode": {
|
||||
"message": "Invalid verification code"
|
||||
},
|
||||
|
|
|
@ -21,6 +21,7 @@ import { LinkModule, TypographyModule, CheckboxModule, DialogService } from "@bi
|
|||
|
||||
import { TwoFactorAuthAuthenticatorComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
|
||||
import { TwoFactorAuthEmailComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component";
|
||||
import { TwoFactorAuthWebAuthnComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component";
|
||||
import { TwoFactorAuthYubikeyComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component";
|
||||
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component";
|
||||
import { TwoFactorOptionsComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component";
|
||||
|
@ -54,6 +55,7 @@ import { FormFieldModule } from "../../../../../libs/components/src/form-field";
|
|||
TwoFactorAuthEmailComponent,
|
||||
TwoFactorAuthAuthenticatorComponent,
|
||||
TwoFactorAuthYubikeyComponent,
|
||||
TwoFactorAuthWebAuthnComponent,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
})
|
||||
|
|
|
@ -5519,6 +5519,9 @@
|
|||
"verificationCodeRequired": {
|
||||
"message": "Verification code is required."
|
||||
},
|
||||
"webauthnCancelOrTimeout": {
|
||||
"message": "The authentication was cancelled or took too long. Please try again."
|
||||
},
|
||||
"invalidVerificationCode": {
|
||||
"message": "Invalid verification code"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<div id="web-authn-frame" class="tw-mb-3" *ngIf="!webAuthnNewTab">
|
||||
<iframe id="webauthn_iframe" sandbox="allow-scripts allow-same-origin"></iframe>
|
||||
</div>
|
||||
<ng-container *ngIf="webAuthnNewTab">
|
||||
<div class="content text-center" *ngIf="webAuthnNewTab">
|
||||
<p class="text-center">{{ "webAuthnNewTab" | i18n }}</p>
|
||||
<button type="button" class="btn primary block" (click)="authWebAuthn()" appStopClick>
|
||||
{{ "webAuthnNewTabOpen" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
|
@ -0,0 +1,131 @@
|
|||
import { DialogModule } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, Inject, Output } from "@angular/core";
|
||||
import { ReactiveFormsModule, FormsModule } from "@angular/forms";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
ButtonModule,
|
||||
LinkModule,
|
||||
TypographyModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-two-factor-auth-webauthn",
|
||||
templateUrl: "two-factor-auth-webauthn.component.html",
|
||||
imports: [
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
DialogModule,
|
||||
ButtonModule,
|
||||
LinkModule,
|
||||
TypographyModule,
|
||||
ReactiveFormsModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
FormsModule,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
})
|
||||
export class TwoFactorAuthWebAuthnComponent {
|
||||
@Output() token = new EventEmitter<string>();
|
||||
|
||||
webAuthnReady = false;
|
||||
webAuthnNewTab = false;
|
||||
webAuthnSupported = false;
|
||||
webAuthn: WebAuthnIFrame = null;
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
@Inject(WINDOW) protected win: Window,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected route: ActivatedRoute,
|
||||
) {
|
||||
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
|
||||
|
||||
if (this.platformUtilsService.getClientType() == ClientType.Browser) {
|
||||
// FIXME: Chromium 110 has broken WebAuthn support in extensions via an iframe
|
||||
this.webAuthnNewTab = true;
|
||||
}
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
if (this.route.snapshot.paramMap.has("webAuthnResponse")) {
|
||||
this.token.emit(this.route.snapshot.paramMap.get("webAuthnResponse"));
|
||||
}
|
||||
|
||||
this.cleanupWebAuthn();
|
||||
|
||||
if (this.win != null && this.webAuthnSupported) {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const webVaultUrl = env.getWebVaultUrl();
|
||||
this.webAuthn = new WebAuthnIFrame(
|
||||
this.win,
|
||||
webVaultUrl,
|
||||
this.webAuthnNewTab,
|
||||
this.platformUtilsService,
|
||||
this.i18nService,
|
||||
(token: string) => {
|
||||
this.token.emit(token);
|
||||
},
|
||||
(error: string) => {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("webauthnCancelOrTimeout"),
|
||||
);
|
||||
},
|
||||
(info: string) => {
|
||||
if (info === "ready") {
|
||||
this.webAuthnReady = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (!this.webAuthnNewTab) {
|
||||
setTimeout(async () => {
|
||||
await this.authWebAuthn();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.cleanupWebAuthn();
|
||||
}
|
||||
|
||||
async authWebAuthn() {
|
||||
const providerData = (await this.twoFactorService.getProviders()).get(
|
||||
TwoFactorProviderType.WebAuthn,
|
||||
);
|
||||
|
||||
if (!this.webAuthnSupported || this.webAuthn == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.webAuthn.init(providerData);
|
||||
}
|
||||
|
||||
private cleanupWebAuthn() {
|
||||
if (this.webAuthn != null) {
|
||||
this.webAuthn.stop();
|
||||
this.webAuthn.cleanup();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,10 @@
|
|||
(token)="token = $event"
|
||||
*ngIf="selectedProviderType === providerType.Yubikey"
|
||||
/>
|
||||
<app-two-factor-auth-webauthn
|
||||
(token)="token = $event; submitForm()"
|
||||
*ngIf="selectedProviderType === providerType.WebAuthn"
|
||||
/>
|
||||
<bit-form-control *ngIf="selectedProviderType != null">
|
||||
<bit-label>{{ "rememberMe" | i18n }}</bit-label>
|
||||
<input type="checkbox" bitCheckbox formControlName="remember" />
|
||||
|
@ -31,7 +35,7 @@
|
|||
buttonType="primary"
|
||||
bitButton
|
||||
bitFormButton
|
||||
*ngIf="selectedProviderType != null"
|
||||
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.WebAuthn"
|
||||
>
|
||||
<span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ actionButtonText }} </span>
|
||||
</button>
|
||||
|
|
|
@ -40,6 +40,7 @@ import { CaptchaProtectedComponent } from "../captcha-protected.component";
|
|||
|
||||
import { TwoFactorAuthAuthenticatorComponent } from "./two-factor-auth-authenticator.component";
|
||||
import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component";
|
||||
import { TwoFactorAuthWebAuthnComponent } from "./two-factor-auth-webauthn.component";
|
||||
import { TwoFactorAuthYubikeyComponent } from "./two-factor-auth-yubikey.component";
|
||||
import {
|
||||
TwoFactorOptionsDialogResult,
|
||||
|
@ -63,6 +64,7 @@ import {
|
|||
TwoFactorAuthAuthenticatorComponent,
|
||||
TwoFactorAuthEmailComponent,
|
||||
TwoFactorAuthYubikeyComponent,
|
||||
TwoFactorAuthWebAuthnComponent,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue