1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-04 18:37:45 +01:00

[PM-8524] Introduce TotpCaptureService and the Browser implementation

This commit is contained in:
Shane Melton 2024-07-09 16:40:45 -07:00
parent afd63c8701
commit 8d08bf797a
No known key found for this signature in database
5 changed files with 68 additions and 5 deletions

View File

@ -17,11 +17,13 @@ import {
CipherFormMode,
CipherFormModule,
DefaultCipherFormConfigService,
TotpCaptureService,
} from "@bitwarden/vault";
import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component";
import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component";
import { BrowserTotpCaptureService } from "../../../services/browser-totp-capture.service";
import { OpenAttachmentsComponent } from "../attachments/open-attachments/open-attachments.component";
/**
@ -80,7 +82,10 @@ export type AddEditQueryParams = Partial<Record<keyof QueryParams, string>>;
selector: "app-add-edit-v2",
templateUrl: "add-edit-v2.component.html",
standalone: true,
providers: [{ provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }],
providers: [
{ provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService },
{ provide: TotpCaptureService, useClass: BrowserTotpCaptureService },
],
imports: [
CommonModule,
SearchModule,

View File

@ -0,0 +1,23 @@
import { Injectable } from "@angular/core";
import qrcodeParser from "qrcode-parser";
import { TotpCaptureService } from "@bitwarden/vault";
import { BrowserApi } from "../../../platform/browser/browser-api";
/**
* Implementation of TotpCaptureService for the browser which captures the
* TOTP secret from the currently visible tab.
*/
@Injectable()
export class BrowserTotpCaptureService implements TotpCaptureService {
async captureTotpSecret() {
const screenshot = await BrowserApi.captureVisibleTab();
const data = await qrcodeParser(screenshot);
const url = new URL(data.toString());
if (url.protocol === "otpauth:" && url.searchParams.has("secret")) {
return data.toString();
}
return null;
}
}

View File

@ -0,0 +1,9 @@
/**
* Service to capture TOTP secret from a client application.
*/
export abstract class TotpCaptureService {
/**
* Captures a TOTP secret and returns it as a string. Returns null if no TOTP secret was found.
*/
abstract captureTotpSecret(): Promise<string | null>;
}

View File

@ -1,5 +1,5 @@
import { DatePipe, NgIf } from "@angular/common";
import { Component, inject, OnInit } from "@angular/core";
import { Component, inject, OnInit, Optional } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { map } from "rxjs";
@ -15,10 +15,12 @@ import {
PopoverModule,
SectionComponent,
SectionHeaderComponent,
ToastService,
TypographyModule,
} from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { TotpCaptureService } from "../../abstractions/totp-capture.service";
import { CipherFormContainer } from "../../cipher-form-container";
@Component({
@ -47,11 +49,11 @@ export class LoginDetailsSectionComponent implements OnInit {
});
/**
* Whether the TOTP field can be captured from the current tab. Only available in the web extension.
* Whether the TOTP field can be captured from the current tab. Only available in the browser extension.
* @protected
*/
protected get canCaptureTotp() {
return false; //BrowserApi.isWebExtensionsApi && this.loginDetailsForm.controls.totp.enabled;
return this.totpCaptureService != null && this.loginDetailsForm.controls.totp.enabled;
}
private datePipe = inject(DatePipe);
@ -83,6 +85,8 @@ export class LoginDetailsSectionComponent implements OnInit {
private formBuilder: FormBuilder,
private i18nService: I18nService,
private generatorService: PasswordGenerationServiceAbstraction,
private toastService: ToastService,
@Optional() private totpCaptureService?: TotpCaptureService,
) {
this.cipherFormContainer.registerChildForm("loginDetails", this.loginDetailsForm);
@ -136,7 +140,28 @@ export class LoginDetailsSectionComponent implements OnInit {
this.loginDetailsForm.controls.password.patchValue(await this.generateNewPassword());
}
captureTotpFromTab = async () => {};
captureTotpFromTab = async () => {
if (!this.canCaptureTotp) {
return;
}
try {
const totp = await this.totpCaptureService.captureTotpSecret();
if (totp) {
this.loginDetailsForm.controls.totp.patchValue(totp);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("totpCaptureSuccess"),
});
}
} catch {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("totpCaptureError"),
});
}
};
removePasskey = async () => {
// Fido2Credentials do not have a form control, so update directly

View File

@ -5,4 +5,5 @@ export {
CipherFormMode,
OptionalInitialValues,
} from "./abstractions/cipher-form-config.service";
export { TotpCaptureService } from "./abstractions/totp-capture.service";
export { DefaultCipherFormConfigService } from "./services/default-cipher-form-config.service";