mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
[PM-14006] Prevent screenshot setting V2 (#12570)
* Add screenshot protection to windows and mac * Update messaging of screencapture prevention feature * Rename settings key * Default allow screenshots * Update screenshot setting description * Fix typo * Add confirm visible prompt
This commit is contained in:
@@ -436,6 +436,23 @@
|
|||||||
"enableSshAgentDesc" | i18n
|
"enableSshAgentDesc" | i18n
|
||||||
}}</small>
|
}}</small>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group" *ngIf="!isLinux">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label for="allowScreenshots">
|
||||||
|
<input
|
||||||
|
id="allowScreenshots"
|
||||||
|
type="checkbox"
|
||||||
|
aria-describedby="allowScreenshotsHelp"
|
||||||
|
formControlName="allowScreenshots"
|
||||||
|
(change)="savePreventScreenshots()"
|
||||||
|
/>
|
||||||
|
{{ "allowScreenshots" | i18n }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<small id="allowScreenshotsHelp" class="help-block">{{
|
||||||
|
"allowScreenshotsDesc" | i18n
|
||||||
|
}}</small>
|
||||||
|
</div>
|
||||||
<div class="form-group" *ngIf="showDuckDuckGoIntegrationOption">
|
<div class="form-group" *ngIf="showDuckDuckGoIntegrationOption">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label for="enableDuckDuckGoBrowserIntegration">
|
<label for="enableDuckDuckGoBrowserIntegration">
|
||||||
|
|||||||
@@ -3,7 +3,16 @@
|
|||||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { FormBuilder } from "@angular/forms";
|
import { FormBuilder } from "@angular/forms";
|
||||||
import { BehaviorSubject, Observable, Subject, firstValueFrom } from "rxjs";
|
import { BehaviorSubject, Observable, Subject, firstValueFrom } from "rxjs";
|
||||||
import { concatMap, debounceTime, filter, map, switchMap, takeUntil, tap } from "rxjs/operators";
|
import {
|
||||||
|
concatMap,
|
||||||
|
debounceTime,
|
||||||
|
filter,
|
||||||
|
map,
|
||||||
|
switchMap,
|
||||||
|
takeUntil,
|
||||||
|
tap,
|
||||||
|
timeout,
|
||||||
|
} from "rxjs/operators";
|
||||||
|
|
||||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||||
@@ -116,6 +125,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
}),
|
}),
|
||||||
enableHardwareAcceleration: true,
|
enableHardwareAcceleration: true,
|
||||||
enableSshAgent: false,
|
enableSshAgent: false,
|
||||||
|
allowScreenshots: false,
|
||||||
enableDuckDuckGoBrowserIntegration: false,
|
enableDuckDuckGoBrowserIntegration: false,
|
||||||
theme: [null as ThemeType | null],
|
theme: [null as ThemeType | null],
|
||||||
locale: [null as string | null],
|
locale: [null as string | null],
|
||||||
@@ -287,6 +297,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
this.desktopSettingsService.hardwareAcceleration$,
|
this.desktopSettingsService.hardwareAcceleration$,
|
||||||
),
|
),
|
||||||
enableSshAgent: await firstValueFrom(this.desktopSettingsService.sshAgentEnabled$),
|
enableSshAgent: await firstValueFrom(this.desktopSettingsService.sshAgentEnabled$),
|
||||||
|
allowScreenshots: !(await firstValueFrom(this.desktopSettingsService.preventScreenshots$)),
|
||||||
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
|
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
|
||||||
locale: await firstValueFrom(this.i18nService.userSetLocale$),
|
locale: await firstValueFrom(this.i18nService.userSetLocale$),
|
||||||
};
|
};
|
||||||
@@ -769,6 +780,33 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
await this.desktopSettingsService.setSshAgentEnabled(this.form.value.enableSshAgent);
|
await this.desktopSettingsService.setSshAgentEnabled(this.form.value.enableSshAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async savePreventScreenshots() {
|
||||||
|
await this.desktopSettingsService.setPreventScreenshots(!this.form.value.allowScreenshots);
|
||||||
|
|
||||||
|
if (!this.form.value.allowScreenshots) {
|
||||||
|
const dialogRef = this.dialogService.openSimpleDialogRef({
|
||||||
|
title: { key: "confirmWindowStillVisibleTitle" },
|
||||||
|
content: { key: "confirmWindowStillVisibleContent" },
|
||||||
|
acceptButtonText: { key: "ok" },
|
||||||
|
cancelButtonText: null,
|
||||||
|
type: "info",
|
||||||
|
});
|
||||||
|
let enabled = true;
|
||||||
|
try {
|
||||||
|
enabled = await firstValueFrom(dialogRef.closed.pipe(timeout(10000)));
|
||||||
|
} catch {
|
||||||
|
enabled = false;
|
||||||
|
} finally {
|
||||||
|
dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
await this.desktopSettingsService.setPreventScreenshots(false);
|
||||||
|
this.form.controls.allowScreenshots.setValue(true, { emitEvent: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async generateVaultTimeoutOptions(): Promise<VaultTimeoutOption[]> {
|
private async generateVaultTimeoutOptions(): Promise<VaultTimeoutOption[]> {
|
||||||
let vaultTimeoutOptions: VaultTimeoutOption[] = [
|
let vaultTimeoutOptions: VaultTimeoutOption[] = [
|
||||||
{ name: this.i18nService.t("oneMinute"), value: 1 },
|
{ name: this.i18nService.t("oneMinute"), value: 1 },
|
||||||
|
|||||||
@@ -3496,6 +3496,12 @@
|
|||||||
"changeAcctEmail": {
|
"changeAcctEmail": {
|
||||||
"message": "Change account email"
|
"message": "Change account email"
|
||||||
},
|
},
|
||||||
|
"allowScreenshots": {
|
||||||
|
"message": "Allow screen capture"
|
||||||
|
},
|
||||||
|
"allowScreenshotsDesc": {
|
||||||
|
"message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays."
|
||||||
|
},
|
||||||
"organizationUpgradeRequired": {
|
"organizationUpgradeRequired": {
|
||||||
"message": "Organization upgrade required"
|
"message": "Organization upgrade required"
|
||||||
},
|
},
|
||||||
@@ -3505,6 +3511,12 @@
|
|||||||
"upgradeOrganizationDesc": {
|
"upgradeOrganizationDesc": {
|
||||||
"message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features."
|
"message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features."
|
||||||
},
|
},
|
||||||
|
"confirmWindowStillVisibleTitle": {
|
||||||
|
"message": "Confirm window still visible"
|
||||||
|
},
|
||||||
|
"confirmWindowStillVisibleContent": {
|
||||||
|
"message": "Please confirm that the window is still visible."
|
||||||
|
},
|
||||||
"updateBrowserOrDisableFingerprintDialogTitle": {
|
"updateBrowserOrDisableFingerprintDialogTitle": {
|
||||||
"message": "Extension update required"
|
"message": "Extension update required"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -76,6 +76,13 @@ export class WindowMain {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.desktopSettingsService.preventScreenshots$.subscribe((prevent) => {
|
||||||
|
if (this.win == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.win.setContentProtection(prevent);
|
||||||
|
});
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
if (!isMacAppStore()) {
|
if (!isMacAppStore()) {
|
||||||
@@ -277,6 +284,14 @@ export class WindowMain {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
firstValueFrom(this.desktopSettingsService.preventScreenshots$)
|
||||||
|
.then((preventScreenshots) => {
|
||||||
|
this.win.setContentProtection(preventScreenshots);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.logService.error(e);
|
||||||
|
});
|
||||||
|
|
||||||
if (this.createWindowCallback) {
|
if (this.createWindowCallback) {
|
||||||
this.createWindowCallback(this.win);
|
this.createWindowCallback(this.win);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,14 @@ const MINIMIZE_ON_COPY = new UserKeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "
|
|||||||
clearOn: [], // User setting, no need to clear
|
clearOn: [], // User setting, no need to clear
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const PREVENT_SCREENSHOTS = new KeyDefinition<boolean>(
|
||||||
|
DESKTOP_SETTINGS_DISK,
|
||||||
|
"preventScreenshots",
|
||||||
|
{
|
||||||
|
deserializer: (b) => b,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Various settings for controlling application behavior specific to the desktop client.
|
* Various settings for controlling application behavior specific to the desktop client.
|
||||||
*/
|
*/
|
||||||
@@ -147,6 +155,13 @@ export class DesktopSettingsService {
|
|||||||
|
|
||||||
sshAgentEnabled$ = this.sshAgentEnabledState.state$.pipe(map(Boolean));
|
sshAgentEnabled$ = this.sshAgentEnabledState.state$.pipe(map(Boolean));
|
||||||
|
|
||||||
|
private readonly preventScreenshotState = this.stateProvider.getGlobal(PREVENT_SCREENSHOTS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application setting for whether or not to allow screenshots of the app.
|
||||||
|
*/
|
||||||
|
preventScreenshots$ = this.preventScreenshotState.state$.pipe(map(Boolean));
|
||||||
|
|
||||||
private readonly minimizeOnCopyState = this.stateProvider.getActive(MINIMIZE_ON_COPY);
|
private readonly minimizeOnCopyState = this.stateProvider.getActive(MINIMIZE_ON_COPY);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -270,4 +285,12 @@ export class DesktopSettingsService {
|
|||||||
async setMinimizeOnCopy(value: boolean, userId: UserId) {
|
async setMinimizeOnCopy(value: boolean, userId: UserId) {
|
||||||
await this.stateProvider.getUser(userId, MINIMIZE_ON_COPY).update(() => value);
|
await this.stateProvider.getUser(userId, MINIMIZE_ON_COPY).update(() => value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the setting for whether or not the screenshot protection is enabled.
|
||||||
|
* @param value `true` if the screenshot protection is enabled, `false` if it is not.
|
||||||
|
*/
|
||||||
|
async setPreventScreenshots(value: boolean) {
|
||||||
|
await this.preventScreenshotState.update(() => value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user