mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 09:43:23 +00:00
Merge branch 'main' into autofill/pm-6546-blurring-of-autofilled-elements-causes-problems-in-blur-event-listeners
This commit is contained in:
@@ -2709,7 +2709,7 @@
|
||||
"message": "Starte DUO und folge den Schritten, um die Anmeldung zu abzuschließen."
|
||||
},
|
||||
"duoRequiredForAccount": {
|
||||
"message": "Duo two-step login is required for your account."
|
||||
"message": "Für dein Konto ist die Duo Zwei-Faktor-Authentifizierung erforderlich."
|
||||
},
|
||||
"popoutTheExtensionToCompleteLogin": {
|
||||
"message": "Popout the extension to complete login."
|
||||
|
||||
@@ -2005,7 +2005,7 @@
|
||||
"message": "Seleccione carpeta..."
|
||||
},
|
||||
"noFoldersFound": {
|
||||
"message": "No folders found",
|
||||
"message": "Ninguna carpeta encontrada",
|
||||
"description": "Used as a message within the notification bar when no folders are found"
|
||||
},
|
||||
"orgPermissionsUpdatedMustSetPassword": {
|
||||
@@ -2670,25 +2670,25 @@
|
||||
"message": "Verificar biométricamente"
|
||||
},
|
||||
"awaitingConfirmation": {
|
||||
"message": "Awaiting confirmation"
|
||||
"message": "Esperando confirmación"
|
||||
},
|
||||
"couldNotCompleteBiometrics": {
|
||||
"message": "Could not complete biometrics."
|
||||
"message": "No se pudo completar la biométrica."
|
||||
},
|
||||
"needADifferentMethod": {
|
||||
"message": "¿Necesita un método distinto?"
|
||||
},
|
||||
"useMasterPassword": {
|
||||
"message": "Use master password"
|
||||
"message": "Usar contraseña maestra"
|
||||
},
|
||||
"usePin": {
|
||||
"message": "Usar NIP"
|
||||
},
|
||||
"useBiometrics": {
|
||||
"message": "Use biometrics"
|
||||
"message": "Usar biométrica"
|
||||
},
|
||||
"enterVerificationCodeSentToEmail": {
|
||||
"message": "Enter the verification code that was sent to your email."
|
||||
"message": "Introduzca el código de verificación que se ha enviado a su correo electrónico."
|
||||
},
|
||||
"resendCode": {
|
||||
"message": "Volver a enviar código"
|
||||
@@ -2706,19 +2706,19 @@
|
||||
}
|
||||
},
|
||||
"launchDuoAndFollowStepsToFinishLoggingIn": {
|
||||
"message": "Launch Duo and follow the steps to finish logging in."
|
||||
"message": "Abra Duo y siga los pasos para terminar de iniciar sesión."
|
||||
},
|
||||
"duoRequiredForAccount": {
|
||||
"message": "Duo two-step login is required for your account."
|
||||
"message": "Se requiere el inicio de sesión en dos pasos Duo para su cuenta."
|
||||
},
|
||||
"popoutTheExtensionToCompleteLogin": {
|
||||
"message": "Popout the extension to complete login."
|
||||
"message": "Abra la extensión para completar el inicio de sesión."
|
||||
},
|
||||
"popoutExtension": {
|
||||
"message": "Popout extension"
|
||||
"message": "Abrir extensión"
|
||||
},
|
||||
"launchDuo": {
|
||||
"message": "Launch Duo"
|
||||
"message": "Iniciar Duo"
|
||||
},
|
||||
"importFormatError": {
|
||||
"message": "Los datos no están formateados correctamente. Por favor, comprueba tu archivo de importación e inténtalo de nuevo."
|
||||
@@ -2995,15 +2995,15 @@
|
||||
"description": "Button text for the setting that allows overriding the default browser autofill settings"
|
||||
},
|
||||
"saveCipherAttemptSuccess": {
|
||||
"message": "Credentials saved successfully!",
|
||||
"message": "¡Credenciales guardadas con éxito!",
|
||||
"description": "Notification message for when saving credentials has succeeded."
|
||||
},
|
||||
"updateCipherAttemptSuccess": {
|
||||
"message": "Credentials updated successfully!",
|
||||
"message": "¡Credenciales actualizadas con éxito!",
|
||||
"description": "Notification message for when updating credentials has succeeded."
|
||||
},
|
||||
"saveCipherAttemptFailed": {
|
||||
"message": "Error saving credentials. Check console for details.",
|
||||
"message": "Se produjo un error al guardar las credenciales. Revise la consola para obtener detalles.",
|
||||
"description": "Notification message for when saving credentials has failed."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,13 +92,13 @@
|
||||
"message": "자동 완성"
|
||||
},
|
||||
"autoFillLogin": {
|
||||
"message": "Auto-fill login"
|
||||
"message": "로그인 자동 완성"
|
||||
},
|
||||
"autoFillCard": {
|
||||
"message": "Auto-fill card"
|
||||
"message": "카드 자동 완성"
|
||||
},
|
||||
"autoFillIdentity": {
|
||||
"message": "Auto-fill identity"
|
||||
"message": "신원 자동 완성"
|
||||
},
|
||||
"generatePasswordCopied": {
|
||||
"message": "비밀번호 생성 및 클립보드에 복사"
|
||||
@@ -110,19 +110,19 @@
|
||||
"message": "사용할 수 있는 로그인이 없습니다."
|
||||
},
|
||||
"noCards": {
|
||||
"message": "No cards"
|
||||
"message": "카드 없음"
|
||||
},
|
||||
"noIdentities": {
|
||||
"message": "No identities"
|
||||
"message": "신원 없음"
|
||||
},
|
||||
"addLoginMenu": {
|
||||
"message": "Add login"
|
||||
"message": "로그인 추가"
|
||||
},
|
||||
"addCardMenu": {
|
||||
"message": "Add card"
|
||||
"message": "카드 추가"
|
||||
},
|
||||
"addIdentityMenu": {
|
||||
"message": "Add identity"
|
||||
"message": "신원 추가"
|
||||
},
|
||||
"unlockVaultMenu": {
|
||||
"message": "보관함 잠금 해제"
|
||||
@@ -220,7 +220,7 @@
|
||||
"message": "도움말 및 의견"
|
||||
},
|
||||
"helpCenter": {
|
||||
"message": "Bitwarden Help center"
|
||||
"message": "Bitwarden 도움말 센터"
|
||||
},
|
||||
"communityForums": {
|
||||
"message": "Explore Bitwarden community forums"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PolicyService as AbstractPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
||||
|
||||
import {
|
||||
CachedServices,
|
||||
@@ -9,11 +10,6 @@ import {
|
||||
stateProviderFactory,
|
||||
StateProviderInitOptions,
|
||||
} from "../../../platform/background/service-factories/state-provider.factory";
|
||||
import {
|
||||
stateServiceFactory as stateServiceFactory,
|
||||
StateServiceInitOptions,
|
||||
} from "../../../platform/background/service-factories/state-service.factory";
|
||||
import { BrowserPolicyService } from "../../services/browser-policy.service";
|
||||
|
||||
import {
|
||||
organizationServiceFactory,
|
||||
@@ -23,7 +19,6 @@ import {
|
||||
type PolicyServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type PolicyServiceInitOptions = PolicyServiceFactoryOptions &
|
||||
StateServiceInitOptions &
|
||||
StateProviderInitOptions &
|
||||
OrganizationServiceInitOptions;
|
||||
|
||||
@@ -36,8 +31,7 @@ export function policyServiceFactory(
|
||||
"policyService",
|
||||
opts,
|
||||
async () =>
|
||||
new BrowserPolicyService(
|
||||
await stateServiceFactory(cache, opts),
|
||||
new PolicyService(
|
||||
await stateProviderFactory(cache, opts),
|
||||
await organizationServiceFactory(cache, opts),
|
||||
),
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
||||
|
||||
import { browserSession, sessionSync } from "../../platform/decorators/session-sync-observable";
|
||||
|
||||
@browserSession
|
||||
export class BrowserPolicyService extends PolicyService {
|
||||
@sessionSync({
|
||||
initializer: (obj: Jsonify<Policy>) => Object.assign(new Policy(), obj),
|
||||
initializeAs: "array",
|
||||
})
|
||||
protected _policies: BehaviorSubject<Policy[]>;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { NOOP_COMMAND_SUFFIX } from "@bitwarden/common/autofill/constants";
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -13,6 +15,7 @@ import { MainContextMenuHandler } from "./main-context-menu-handler";
|
||||
|
||||
describe("context-menu", () => {
|
||||
let stateService: MockProxy<BrowserStateService>;
|
||||
let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
|
||||
@@ -26,6 +29,7 @@ describe("context-menu", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
stateService = mock();
|
||||
autofillSettingsService = mock();
|
||||
i18nService = mock();
|
||||
logService = mock();
|
||||
|
||||
@@ -41,14 +45,20 @@ describe("context-menu", () => {
|
||||
});
|
||||
|
||||
i18nService.t.mockImplementation((key) => key);
|
||||
sut = new MainContextMenuHandler(stateService, i18nService, logService);
|
||||
sut = new MainContextMenuHandler(
|
||||
stateService,
|
||||
autofillSettingsService,
|
||||
i18nService,
|
||||
logService,
|
||||
);
|
||||
autofillSettingsService.enableContextMenu$ = of(true);
|
||||
});
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
describe("init", () => {
|
||||
it("has menu disabled", async () => {
|
||||
stateService.getDisableContextMenuItem.mockResolvedValue(true);
|
||||
autofillSettingsService.enableContextMenu$ = of(false);
|
||||
|
||||
const createdMenu = await sut.init();
|
||||
expect(createdMenu).toBeFalsy();
|
||||
@@ -56,8 +66,6 @@ describe("context-menu", () => {
|
||||
});
|
||||
|
||||
it("has menu enabled, but does not have premium", async () => {
|
||||
stateService.getDisableContextMenuItem.mockResolvedValue(false);
|
||||
|
||||
stateService.getCanAccessPremium.mockResolvedValue(false);
|
||||
|
||||
const createdMenu = await sut.init();
|
||||
@@ -66,8 +74,6 @@ describe("context-menu", () => {
|
||||
});
|
||||
|
||||
it("has menu enabled and has premium", async () => {
|
||||
stateService.getDisableContextMenuItem.mockResolvedValue(false);
|
||||
|
||||
stateService.getCanAccessPremium.mockResolvedValue(true);
|
||||
|
||||
const createdMenu = await sut.init();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import {
|
||||
AUTOFILL_CARD_ID,
|
||||
AUTOFILL_ID,
|
||||
@@ -14,6 +16,7 @@ import {
|
||||
ROOT_ID,
|
||||
SEPARATOR_ID,
|
||||
} from "@bitwarden/common/autofill/constants";
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||
@@ -22,6 +25,7 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
import { autofillSettingsServiceFactory } from "../../autofill/background/service_factories/autofill-settings-service.factory";
|
||||
import { Account } from "../../models/account";
|
||||
import { CachedServices } from "../../platform/background/service-factories/factory-options";
|
||||
import {
|
||||
@@ -156,6 +160,7 @@ export class MainContextMenuHandler {
|
||||
|
||||
constructor(
|
||||
private stateService: BrowserStateService,
|
||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||
private i18nService: I18nService,
|
||||
private logService: LogService,
|
||||
) {}
|
||||
@@ -183,6 +188,7 @@ export class MainContextMenuHandler {
|
||||
|
||||
return new MainContextMenuHandler(
|
||||
await stateServiceFactory(cachedServices, serviceOptions),
|
||||
await autofillSettingsServiceFactory(cachedServices, serviceOptions),
|
||||
await i18nServiceFactory(cachedServices, serviceOptions),
|
||||
await logServiceFactory(cachedServices, serviceOptions),
|
||||
);
|
||||
@@ -193,8 +199,8 @@ export class MainContextMenuHandler {
|
||||
* @returns a boolean showing whether or not items were created
|
||||
*/
|
||||
async init(): Promise<boolean> {
|
||||
const menuDisabled = await this.stateService.getDisableContextMenuItem();
|
||||
if (menuDisabled) {
|
||||
const menuEnabled = await firstValueFrom(this.autofillSettingsService.enableContextMenu$);
|
||||
if (!menuEnabled) {
|
||||
await MainContextMenuHandler.removeAll();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs
|
||||
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
|
||||
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
@@ -175,7 +176,6 @@ import {
|
||||
} from "@bitwarden/vault-export-core";
|
||||
|
||||
import { BrowserOrganizationService } from "../admin-console/services/browser-organization.service";
|
||||
import { BrowserPolicyService } from "../admin-console/services/browser-policy.service";
|
||||
import ContextMenusBackground from "../autofill/background/context-menus.background";
|
||||
import NotificationBackground from "../autofill/background/notification.background";
|
||||
import OverlayBackground from "../autofill/background/overlay.background";
|
||||
@@ -501,11 +501,7 @@ export default class MainBackground {
|
||||
this.stateService,
|
||||
this.stateProvider,
|
||||
);
|
||||
this.policyService = new BrowserPolicyService(
|
||||
this.stateService,
|
||||
this.stateProvider,
|
||||
this.organizationService,
|
||||
);
|
||||
this.policyService = new PolicyService(this.stateProvider, this.organizationService);
|
||||
this.autofillSettingsService = new AutofillSettingsService(
|
||||
this.stateProvider,
|
||||
this.policyService,
|
||||
@@ -818,6 +814,7 @@ export default class MainBackground {
|
||||
this.stateService,
|
||||
this.autofillSettingsService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.biometricStateService,
|
||||
);
|
||||
|
||||
// Other fields
|
||||
@@ -953,6 +950,7 @@ export default class MainBackground {
|
||||
if (!this.popupOnlyContext) {
|
||||
this.mainContextMenuHandler = new MainContextMenuHandler(
|
||||
this.stateService,
|
||||
this.autofillSettingsService,
|
||||
this.i18nService,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
@@ -84,10 +84,6 @@ export class NativeMessagingBackground {
|
||||
private authService: AuthService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.stateService.setBiometricFingerprintValidated(false);
|
||||
|
||||
if (chrome?.permissions?.onAdded) {
|
||||
// Reload extension to activate nativeMessaging
|
||||
chrome.permissions.onAdded.addListener((permissions) => {
|
||||
@@ -100,9 +96,7 @@ export class NativeMessagingBackground {
|
||||
|
||||
async connect() {
|
||||
this.appId = await this.appIdService.getAppId();
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.stateService.setBiometricFingerprintValidated(false);
|
||||
await this.biometricStateService.setFingerprintValidated(false);
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.port = BrowserApi.connectNative("com.8bit.bitwarden");
|
||||
@@ -148,9 +142,7 @@ export class NativeMessagingBackground {
|
||||
|
||||
if (this.validatingFingerprint) {
|
||||
this.validatingFingerprint = false;
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.stateService.setBiometricFingerprintValidated(true);
|
||||
await this.biometricStateService.setFingerprintValidated(true);
|
||||
}
|
||||
this.sharedSecret = new SymmetricCryptoKey(decrypted);
|
||||
this.secureSetupResolve();
|
||||
|
||||
@@ -102,7 +102,6 @@ import { ImportServiceAbstraction } from "@bitwarden/importer/core";
|
||||
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
|
||||
|
||||
import { BrowserOrganizationService } from "../../admin-console/services/browser-organization.service";
|
||||
import { BrowserPolicyService } from "../../admin-console/services/browser-policy.service";
|
||||
import { UnauthGuardService } from "../../auth/popup/services";
|
||||
import { AutofillService } from "../../autofill/services/abstractions/autofill.service";
|
||||
import MainBackground from "../../background/main.background";
|
||||
@@ -238,8 +237,9 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
},
|
||||
{
|
||||
provide: LogServiceAbstraction,
|
||||
useFactory: getBgService<ConsoleLogService>("logService"),
|
||||
deps: [],
|
||||
useFactory: (platformUtilsService: PlatformUtilsService) =>
|
||||
new ConsoleLogService(platformUtilsService.isDev()),
|
||||
deps: [PlatformUtilsService],
|
||||
},
|
||||
{
|
||||
provide: BrowserEnvironmentService,
|
||||
@@ -293,17 +293,6 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
useFactory: getBgService<EventCollectionService>("eventCollectionService"),
|
||||
deps: [],
|
||||
},
|
||||
{
|
||||
provide: PolicyService,
|
||||
useFactory: (
|
||||
stateService: StateServiceAbstraction,
|
||||
stateProvider: StateProvider,
|
||||
organizationService: OrganizationService,
|
||||
) => {
|
||||
return new BrowserPolicyService(stateService, stateProvider, organizationService);
|
||||
},
|
||||
deps: [StateServiceAbstraction, StateProvider, OrganizationService],
|
||||
},
|
||||
{
|
||||
provide: PlatformUtilsService,
|
||||
useFactory: getBgService<PlatformUtilsService>("platformUtilsService"),
|
||||
|
||||
@@ -105,7 +105,9 @@ export class OptionsComponent implements OnInit {
|
||||
this.userNotificationSettingsService.enableChangedPasswordPrompt$,
|
||||
);
|
||||
|
||||
this.enableContextMenuItem = !(await this.stateService.getDisableContextMenuItem());
|
||||
this.enableContextMenuItem = await firstValueFrom(
|
||||
this.autofillSettingsService.enableContextMenu$,
|
||||
);
|
||||
|
||||
this.showCardsCurrentTab = !(await this.stateService.getDontShowCardsCurrentTab());
|
||||
this.showIdentitiesCurrentTab = !(await this.stateService.getDontShowIdentitiesCurrentTab());
|
||||
@@ -143,7 +145,7 @@ export class OptionsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async updateContextMenuItem() {
|
||||
await this.stateService.setDisableContextMenuItem(!this.enableContextMenuItem);
|
||||
await this.autofillSettingsService.setEnableContextMenu(this.enableContextMenuItem);
|
||||
this.messagingService.send("bgUpdateContextMenu");
|
||||
}
|
||||
|
||||
|
||||
@@ -415,7 +415,7 @@ export class SettingsComponent implements OnInit {
|
||||
]);
|
||||
} else {
|
||||
await this.biometricStateService.setBiometricUnlockEnabled(false);
|
||||
await this.stateService.setBiometricFingerprintValidated(false);
|
||||
await this.biometricStateService.setFingerprintValidated(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"papaparse": "5.4.1",
|
||||
"proper-lockfile": "4.1.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tldts": "6.1.11",
|
||||
"tldts": "6.1.13",
|
||||
"zxcvbn": "4.4.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,11 +394,7 @@ export class Main {
|
||||
|
||||
this.organizationUserService = new OrganizationUserServiceImplementation(this.apiService);
|
||||
|
||||
this.policyService = new PolicyService(
|
||||
this.stateService,
|
||||
this.stateProvider,
|
||||
this.organizationService,
|
||||
);
|
||||
this.policyService = new PolicyService(this.stateProvider, this.organizationService);
|
||||
|
||||
this.policyApiService = new PolicyApiService(this.policyService, this.apiService);
|
||||
|
||||
@@ -653,7 +649,7 @@ export class Main {
|
||||
this.cipherService.clear(userId),
|
||||
this.folderService.clear(userId),
|
||||
this.collectionService.clear(userId as UserId),
|
||||
this.policyService.clear(userId),
|
||||
this.policyService.clear(userId as UserId),
|
||||
this.passwordGenerationService.clear(),
|
||||
this.providerService.save(null, userId as UserId),
|
||||
]);
|
||||
|
||||
98
apps/desktop/desktop_native/Cargo.lock
generated
98
apps/desktop/desktop_native/Cargo.lock
generated
@@ -510,9 +510,9 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.18.4"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73"
|
||||
checksum = "2eae10b27b6dd27e22ed0d812c6387deba295e6fc004a8b379e459b663b05a02"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -521,7 +521,6 @@ dependencies = [
|
||||
"gio-sys",
|
||||
"glib",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
@@ -529,22 +528,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.18.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
|
||||
checksum = "bcf8e1d9219bb294636753d307b030c1e8a032062cba74f493c431a5c8b81ce4"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"winapi",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.18.2"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c316afb01ce8067c5eaab1fc4f2cd47dc21ce7b6296358605e2ffab23ccbd19"
|
||||
checksum = "ab9e86540b5d8402e905ad4ce7d6aa544092131ab564f3102175af176b90a053"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"futures-channel",
|
||||
@@ -558,20 +557,18 @@ dependencies = [
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib-macros"
|
||||
version = "0.18.2"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8da903822b136d42360518653fcf154455defc437d3e7a81475bf9a95ff1e47"
|
||||
checksum = "0f5897ca27a83e4cdc7b4666850bade0a2e73e17689aabafcc9acddad9d823b8"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-crate",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
@@ -579,9 +576,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.18.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
|
||||
checksum = "630f097773d7c7a0bb3258df4e8157b47dc98bbfa0e60ad9ab56174813feced4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
@@ -589,9 +586,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
|
||||
checksum = "c85e2b1080b9418dd0c58b498da3a5c826030343e0ef07bde6a955d28de54979"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
@@ -681,9 +678,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libsecret"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac6fae6ebe590e06ef9d01b125e46b7d4c05ccbd5961f12b4aefe2ecd010220f"
|
||||
checksum = "50c6ccddc706a38eca477b4d7857acd6c76c7d6fba5d47b4b2e7d800e5a17194"
|
||||
dependencies = [
|
||||
"gio",
|
||||
"glib",
|
||||
@@ -693,9 +690,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libsecret-sys"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b716fc5e1c82eb0d28665882628382ab0e0a156a6d73580e33f0ac6ac8d2540"
|
||||
checksum = "3a1af48e61f1c8e77e9705296f346e45b637754a92348a79b4c62df84d0654c2"
|
||||
dependencies = [
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
@@ -747,9 +744,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
@@ -990,36 +987,11 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
||||
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"toml_edit 0.19.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
"toml_edit 0.21.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1252,9 +1224,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.1"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
|
||||
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
@@ -1406,17 +1378,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.20.7"
|
||||
@@ -1430,6 +1391,17 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree_magic_mini"
|
||||
version = "3.0.3"
|
||||
|
||||
@@ -54,5 +54,5 @@ security-framework = "=2.9.2"
|
||||
security-framework-sys = "=2.9.1"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
gio = "=0.18.4"
|
||||
libsecret = "=0.4.0"
|
||||
gio = "=0.19.2"
|
||||
libsecret = "=0.5.0"
|
||||
|
||||
@@ -126,6 +126,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
||||
StateServiceAbstraction,
|
||||
AutofillSettingsServiceAbstraction,
|
||||
VaultTimeoutSettingsService,
|
||||
BiometricStateService,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<button type="submit" class="btn primary block" [disabled]="!accessibilityForm.valid">
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
<button type="button" routerLink="/login" class="btn block">{{ "done" | i18n }}</button>
|
||||
<button type="button" (click)="close()" class="btn block">{{ "done" | i18n }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -2,14 +2,11 @@ import { Component, NgZone } from "@angular/core";
|
||||
import { UntypedFormControl, UntypedFormGroup, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
const BroadcasterSubscriptionId = "AccessibilityCookieComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-accessibility-cookie",
|
||||
templateUrl: "accessibility-cookie.component.html",
|
||||
@@ -27,40 +24,21 @@ export class AccessibilityCookieComponent {
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected i18nService: I18nService,
|
||||
private broadcasterService: BroadcasterService,
|
||||
protected ngZone: NgZone,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
this.ngZone.run(() => {
|
||||
switch (message.command) {
|
||||
case "windowIsFocused":
|
||||
if (this.listenForCookie) {
|
||||
this.listenForCookie = false;
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.checkForCookie();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
registerhCaptcha() {
|
||||
this.platformUtilsService.launchUri("https://www.hcaptcha.com/accessibility");
|
||||
}
|
||||
|
||||
async checkForCookie() {
|
||||
this.hCaptchaWindow.close();
|
||||
async close() {
|
||||
const [cookie] = await ipc.auth.getHcaptchaAccessibilityCookie();
|
||||
if (cookie) {
|
||||
this.onCookieSavedSuccess();
|
||||
} else {
|
||||
this.onCookieSavedFailure();
|
||||
}
|
||||
await this.router.navigate(["/login"]);
|
||||
}
|
||||
|
||||
onCookieSavedSuccess() {
|
||||
@@ -89,10 +67,6 @@ export class AccessibilityCookieComponent {
|
||||
return;
|
||||
}
|
||||
this.listenForCookie = true;
|
||||
this.hCaptchaWindow = window.open(this.accessibilityForm.value.link);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
window.open(this.accessibilityForm.value.link, "_blank", "noopener noreferrer");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export class WindowMain {
|
||||
private windowStateChangeTimer: NodeJS.Timeout;
|
||||
private windowStates: { [key: string]: WindowState } = {};
|
||||
private enableAlwaysOnTop = false;
|
||||
private session: Electron.Session;
|
||||
session: Electron.Session;
|
||||
|
||||
readonly defaultWidth = 950;
|
||||
readonly defaultHeight = 600;
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import * as path from "path";
|
||||
|
||||
import {
|
||||
app,
|
||||
dialog,
|
||||
ipcMain,
|
||||
Menu,
|
||||
MenuItem,
|
||||
nativeTheme,
|
||||
session,
|
||||
Notification,
|
||||
shell,
|
||||
} from "electron";
|
||||
import { app, dialog, ipcMain, Menu, MenuItem, nativeTheme, Notification, shell } from "electron";
|
||||
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||
@@ -64,7 +54,7 @@ export class ElectronMainMessagingService implements MessagingService {
|
||||
});
|
||||
|
||||
ipcMain.handle("getCookie", async (event, options) => {
|
||||
return await session.defaultSession.cookies.get(options);
|
||||
return await this.windowMain.session.cookies.get(options);
|
||||
});
|
||||
|
||||
ipcMain.handle("loginRequest", async (event, options) => {
|
||||
|
||||
@@ -1,52 +1,37 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title" id="confirmUserTitle">
|
||||
{{ "confirmUser" | i18n }}
|
||||
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
<form [formGroup]="confirmForm" [bitSubmit]="submit">
|
||||
<bit-dialog dialogSize="large" [loading]="loading">
|
||||
<span bitDialogTitle>
|
||||
{{ "confirmUser" | i18n }}
|
||||
<small class="tw-text-muted">{{ params.name }}</small>
|
||||
</span>
|
||||
<div bitDialogContent>
|
||||
<p bitTypography="body1">
|
||||
{{ "fingerprintEnsureIntegrityVerify" | i18n }}
|
||||
<a
|
||||
bitLink
|
||||
href="https://bitwarden.com/help/fingerprint-phrase/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
{{ "fingerprintEnsureIntegrityVerify" | i18n }}
|
||||
<a href="https://bitwarden.com/help/fingerprint-phrase/" target="_blank" rel="noreferrer">
|
||||
{{ "learnMore" | i18n }}</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<code>{{ fingerprint }}</code>
|
||||
</p>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="dontAskAgain"
|
||||
name="DontAskAgain"
|
||||
[(ngModel)]="dontAskAgain"
|
||||
/>
|
||||
<label class="form-check-label" for="dontAskAgain">
|
||||
{{ "dontAskFingerprintAgain" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "confirm" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ "learnMore" | i18n }}</a
|
||||
>
|
||||
</p>
|
||||
<p bitTypography="body1">
|
||||
<code>{{ fingerprint }}</code>
|
||||
</p>
|
||||
|
||||
<bit-form-control>
|
||||
<input type="checkbox" bitCheckbox formControlName="dontAskAgain" />
|
||||
<bit-label> {{ "dontAskFingerprintAgain" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</div>
|
||||
<div bitDialogFooter>
|
||||
<button type="submit" buttonType="primary" bitButton bitFormButton>
|
||||
<span>{{ "confirm" | i18n }}</span>
|
||||
</button>
|
||||
<button bitButton bitFormButton buttonType="secondary" type="button" bitDialogClose>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
|
||||
@@ -1,39 +1,52 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, OnInit, Inject } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
export enum EmergencyAccessConfirmDialogResult {
|
||||
Confirmed = "confirmed",
|
||||
}
|
||||
type EmergencyAccessConfirmDialogData = {
|
||||
/** display name of the account requesting emergency access */
|
||||
name: string;
|
||||
/** identifies the account requesting emergency access */
|
||||
userId: string;
|
||||
/** traces a unique emergency request */
|
||||
emergencyAccessId: string;
|
||||
};
|
||||
@Component({
|
||||
selector: "emergency-access-confirm",
|
||||
templateUrl: "emergency-access-confirm.component.html",
|
||||
})
|
||||
export class EmergencyAccessConfirmComponent implements OnInit {
|
||||
@Input() name: string;
|
||||
@Input() userId: string;
|
||||
@Input() emergencyAccessId: string;
|
||||
@Input() formPromise: Promise<any>;
|
||||
@Output() onConfirmed = new EventEmitter();
|
||||
|
||||
dontAskAgain = false;
|
||||
loading = true;
|
||||
fingerprint: string;
|
||||
confirmForm = this.formBuilder.group({
|
||||
dontAskAgain: [false],
|
||||
});
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected params: EmergencyAccessConfirmDialogData,
|
||||
private formBuilder: FormBuilder,
|
||||
private apiService: ApiService,
|
||||
private cryptoService: CryptoService,
|
||||
private stateService: StateService,
|
||||
private logService: LogService,
|
||||
private dialogRef: DialogRef<EmergencyAccessConfirmDialogResult>,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
try {
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId);
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(this.params.userId);
|
||||
if (publicKeyResponse != null) {
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
const fingerprint = await this.cryptoService.getFingerprint(this.userId, publicKey);
|
||||
const fingerprint = await this.cryptoService.getFingerprint(this.params.userId, publicKey);
|
||||
if (fingerprint != null) {
|
||||
this.fingerprint = fingerprint.join("-");
|
||||
}
|
||||
@@ -44,19 +57,33 @@ export class EmergencyAccessConfirmComponent implements OnInit {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
submit = async () => {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dontAskAgain) {
|
||||
if (this.confirmForm.get("dontAskAgain").value) {
|
||||
await this.stateService.setAutoConfirmFingerprints(true);
|
||||
}
|
||||
|
||||
try {
|
||||
this.onConfirmed.emit();
|
||||
this.dialogRef.close(EmergencyAccessConfirmDialogResult.Confirmed);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Strongly typed helper to open a EmergencyAccessConfirmComponent
|
||||
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||
* @param config Configuration for the dialog
|
||||
*/
|
||||
static open(
|
||||
dialogService: DialogService,
|
||||
config: DialogConfig<EmergencyAccessConfirmDialogData>,
|
||||
) {
|
||||
return dialogService.open<EmergencyAccessConfirmDialogResult>(
|
||||
EmergencyAccessConfirmComponent,
|
||||
config,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,142 +1,68 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
|
||||
<form
|
||||
class="modal-content"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title" id="userAddEditTitle">
|
||||
<app-premium-badge *ngIf="readOnly"></app-premium-badge>
|
||||
{{ title }}
|
||||
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!loading">
|
||||
<ng-container *ngIf="!editMode">
|
||||
<p>{{ "inviteEmergencyContactDesc" | i18n }}</p>
|
||||
<div class="form-group mb-4">
|
||||
<label for="email">{{ "email" | i18n }}</label>
|
||||
<input
|
||||
id="email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Email"
|
||||
[(ngModel)]="email"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h3>
|
||||
<form [formGroup]="addEditForm" [bitSubmit]="submit">
|
||||
<bit-dialog dialogSize="large" [loading]="loading">
|
||||
<span bitDialogTitle>
|
||||
<app-premium-badge *ngIf="readOnly"></app-premium-badge>
|
||||
{{ title }}
|
||||
<small class="tw-text-muted" *ngIf="params.name">{{ params.name }}</small>
|
||||
</span>
|
||||
<ng-container bitDialogContent>
|
||||
<ng-container *ngIf="!editMode">
|
||||
<p bitTypography="body1">{{ "inviteEmergencyContactDesc" | i18n }}</p>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "email" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="email" />
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
<bit-radio-group formControlName="emergencyAccessType" [block]="true">
|
||||
<bit-label>
|
||||
{{ "userAccess" | i18n }}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
bitLink
|
||||
linkType="primary"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
href="https://bitwarden.com/help/emergency-access/#user-access"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="form-check mt-2 form-check-block">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="userType"
|
||||
id="emergencyTypeView"
|
||||
[value]="emergencyAccessType.View"
|
||||
[(ngModel)]="type"
|
||||
/>
|
||||
<label class="form-check-label" for="emergencyTypeView">
|
||||
{{ "view" | i18n }}
|
||||
<small>{{ "viewDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2 form-check-block">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="userType"
|
||||
id="emergencyTypeTakeover"
|
||||
[value]="emergencyAccessType.Takeover"
|
||||
[(ngModel)]="type"
|
||||
[disabled]="readOnly"
|
||||
/>
|
||||
<label class="form-check-label" for="emergencyTypeTakeover">
|
||||
{{ "takeover" | i18n }}
|
||||
<small>{{ "takeoverDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group col-6 mt-4">
|
||||
<label for="waitTime">{{ "waitTime" | i18n }}</label>
|
||||
<select
|
||||
id="waitTime"
|
||||
name="waitTime"
|
||||
[(ngModel)]="waitTime"
|
||||
class="form-control"
|
||||
[disabled]="readOnly"
|
||||
>
|
||||
<option *ngFor="let o of waitTimes" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small class="text-muted">{{ "waitTimeDesc" | i18n }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
buttonType="primary"
|
||||
bitButton
|
||||
[loading]="loading || form.loading"
|
||||
[disabled]="readOnly"
|
||||
>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button bitButton buttonType="secondary" type="button" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
#deleteBtn
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
type="button"
|
||||
(click)="delete()"
|
||||
appA11yTitle="{{ 'delete' | i18n }}"
|
||||
*ngIf="editMode"
|
||||
[disabled]="$any(deleteBtn).loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-trash bwi-lg bwi-fw"
|
||||
[hidden]="$any(deleteBtn).loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
||||
[hidden]="!$any(deleteBtn).loading"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</bit-label>
|
||||
<bit-radio-button id="emergencyTypeView" [value]="emergencyAccessType.View">
|
||||
<bit-label>{{ "view" | i18n }}</bit-label>
|
||||
<bit-hint>{{ "viewDesc" | i18n }}</bit-hint>
|
||||
</bit-radio-button>
|
||||
|
||||
<bit-radio-button id="emergencyTypeTakeover" [value]="emergencyAccessType.Takeover">
|
||||
<bit-label>{{ "takeover" | i18n }}</bit-label>
|
||||
<bit-hint>{{ "takeoverDesc" | i18n }}</bit-hint>
|
||||
</bit-radio-button>
|
||||
</bit-radio-group>
|
||||
|
||||
<bit-form-field class="tw-w-1/2 tw-relative tw-px-2.5">
|
||||
<bit-label>{{ "waitTime" | i18n }}</bit-label>
|
||||
<bit-select formControlName="waitTime">
|
||||
<bit-option *ngFor="let o of waitTimes" [value]="o.value" [label]="o.name"></bit-option>
|
||||
</bit-select>
|
||||
<bit-hint class="tw-text-sm">{{ "waitTimeDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="submit" buttonType="primary" bitButton bitFormButton [disabled]="readOnly">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button bitButton bitFormButton buttonType="secondary" type="button" bitDialogClose>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitFormButton
|
||||
class="tw-ml-auto"
|
||||
bitIconButton="bwi-trash"
|
||||
buttonType="danger"
|
||||
[bitAction]="delete"
|
||||
*ngIf="editMode"
|
||||
appA11yTitle="{{ 'delete' | i18n }}"
|
||||
></button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
|
||||
@@ -1,45 +1,59 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { EmergencyAccessService } from "../../emergency-access";
|
||||
import { EmergencyAccessType } from "../../emergency-access/enums/emergency-access-type";
|
||||
|
||||
export type EmergencyAccessAddEditDialogData = {
|
||||
/** display name of the account requesting emergency access */
|
||||
name: string;
|
||||
/** traces a unique emergency request */
|
||||
emergencyAccessId: string;
|
||||
/** A boolean indicating whether the emergency access request is in read-only mode (true for view-only, false for editing). */
|
||||
readOnly: boolean;
|
||||
};
|
||||
|
||||
export enum EmergencyAccessAddEditDialogResult {
|
||||
Saved = "saved",
|
||||
Canceled = "canceled",
|
||||
Deleted = "deleted",
|
||||
}
|
||||
@Component({
|
||||
selector: "emergency-access-add-edit",
|
||||
templateUrl: "emergency-access-add-edit.component.html",
|
||||
})
|
||||
export class EmergencyAccessAddEditComponent implements OnInit {
|
||||
@Input() name: string;
|
||||
@Input() emergencyAccessId: string;
|
||||
@Output() onSaved = new EventEmitter();
|
||||
@Output() onDeleted = new EventEmitter();
|
||||
|
||||
loading = true;
|
||||
readOnly = false;
|
||||
editMode = false;
|
||||
title: string;
|
||||
email: string;
|
||||
type: EmergencyAccessType = EmergencyAccessType.View;
|
||||
|
||||
formPromise: Promise<any>;
|
||||
|
||||
emergencyAccessType = EmergencyAccessType;
|
||||
waitTimes: { name: string; value: number }[];
|
||||
waitTime: number;
|
||||
|
||||
addEditForm = this.formBuilder.group({
|
||||
email: ["", [Validators.email, Validators.required]],
|
||||
emergencyAccessType: [this.emergencyAccessType.View],
|
||||
waitTime: [{ value: null, disabled: this.readOnly }, [Validators.required]],
|
||||
});
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected params: EmergencyAccessAddEditDialogData,
|
||||
private formBuilder: FormBuilder,
|
||||
private emergencyAccessService: EmergencyAccessService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService,
|
||||
private dialogRef: DialogRef<EmergencyAccessAddEditDialogResult>,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.editMode = this.loading = this.emergencyAccessId != null;
|
||||
|
||||
this.editMode = this.loading = this.params.emergencyAccessId != null;
|
||||
this.waitTimes = [
|
||||
{ name: this.i18nService.t("oneDay"), value: 1 },
|
||||
{ name: this.i18nService.t("days", "2"), value: 2 },
|
||||
@@ -50,46 +64,72 @@ export class EmergencyAccessAddEditComponent implements OnInit {
|
||||
];
|
||||
|
||||
if (this.editMode) {
|
||||
this.editMode = true;
|
||||
this.title = this.i18nService.t("editEmergencyContact");
|
||||
try {
|
||||
const emergencyAccess = await this.emergencyAccessService.getEmergencyAccess(
|
||||
this.emergencyAccessId,
|
||||
this.params.emergencyAccessId,
|
||||
);
|
||||
this.type = emergencyAccess.type;
|
||||
this.waitTime = emergencyAccess.waitTimeDays;
|
||||
this.addEditForm.patchValue({
|
||||
email: emergencyAccess.email,
|
||||
waitTime: emergencyAccess.waitTimeDays,
|
||||
emergencyAccessType: emergencyAccess.type,
|
||||
});
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
} else {
|
||||
this.title = this.i18nService.t("inviteEmergencyContact");
|
||||
this.waitTime = this.waitTimes[2].value;
|
||||
this.addEditForm.patchValue({ waitTime: this.waitTimes[2].value });
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
submit = async () => {
|
||||
if (this.addEditForm.invalid) {
|
||||
this.addEditForm.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (this.editMode) {
|
||||
await this.emergencyAccessService.update(this.emergencyAccessId, this.type, this.waitTime);
|
||||
await this.emergencyAccessService.update(
|
||||
this.params.emergencyAccessId,
|
||||
this.addEditForm.value.emergencyAccessType,
|
||||
this.addEditForm.value.waitTime,
|
||||
);
|
||||
} else {
|
||||
await this.emergencyAccessService.invite(this.email, this.type, this.waitTime);
|
||||
await this.emergencyAccessService.invite(
|
||||
this.addEditForm.value.email,
|
||||
this.addEditForm.value.emergencyAccessType,
|
||||
this.addEditForm.value.waitTime,
|
||||
);
|
||||
}
|
||||
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.name),
|
||||
this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.params.name),
|
||||
);
|
||||
this.onSaved.emit();
|
||||
this.dialogRef.close(EmergencyAccessAddEditDialogResult.Saved);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async delete() {
|
||||
this.onDeleted.emit();
|
||||
}
|
||||
delete = async () => {
|
||||
this.dialogRef.close(EmergencyAccessAddEditDialogResult.Deleted);
|
||||
};
|
||||
/**
|
||||
* Strongly typed helper to open a EmergencyAccessAddEditComponent
|
||||
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||
* @param config Configuration for the dialog
|
||||
*/
|
||||
static open = (
|
||||
dialogService: DialogService,
|
||||
config: DialogConfig<EmergencyAccessAddEditDialogData>,
|
||||
) => {
|
||||
return dialogService.open<EmergencyAccessAddEditDialogResult>(
|
||||
EmergencyAccessAddEditComponent,
|
||||
config,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,264 +1,276 @@
|
||||
<app-header></app-header>
|
||||
|
||||
<bit-container>
|
||||
<p>
|
||||
{{ "emergencyAccessDesc" | i18n }}
|
||||
<a href="https://bitwarden.com/help/emergency-access/" target="_blank" rel="noreferrer">
|
||||
{{ "learnMore" | i18n }}.
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p *ngIf="isOrganizationOwner">
|
||||
<b>{{ "warning" | i18n }}:</b> {{ "emergencyAccessOwnerWarning" | i18n }}
|
||||
</p>
|
||||
|
||||
<div class="page-header d-flex">
|
||||
<h2>
|
||||
{{ "trustedEmergencyContacts" | i18n }}
|
||||
<app-premium-badge></app-premium-badge>
|
||||
</h2>
|
||||
<div class="ml-auto d-flex">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-primary ml-3"
|
||||
type="button"
|
||||
(click)="invite()"
|
||||
[disabled]="!canAccessPremium"
|
||||
<bit-section>
|
||||
<p bitTypography="body1">
|
||||
<span class="tw-text-main">{{ "emergencyAccessDesc" | i18n }}</span>
|
||||
<a
|
||||
bitLink
|
||||
href="https://bitwarden.com/help/emergency-access/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-plus bwi-fw"></i>
|
||||
{{ "addEmergencyContact" | i18n }}
|
||||
</button>
|
||||
{{ "learnMore" | i18n }}.
|
||||
</a>
|
||||
</p>
|
||||
<bit-callout *ngIf="isOrganizationOwner" type="warning" title="{{ 'warning' | i18n }}">{{
|
||||
"emergencyAccessOwnerWarning" | i18n
|
||||
}}</bit-callout>
|
||||
</bit-section>
|
||||
<bit-section>
|
||||
<div class="tw-flex tw-items-center tw-gap-2 tw-mb-2">
|
||||
<h2 bitTypography="h2" noMargin class="tw-mb-0">
|
||||
{{ "trustedEmergencyContacts" | i18n }}
|
||||
</h2>
|
||||
<app-premium-badge></app-premium-badge>
|
||||
<div class="tw-ml-auto tw-flex">
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
[bitAction]="invite"
|
||||
[disabled]="!canAccessPremium"
|
||||
>
|
||||
<i aria-hidden="true" class="bwi bwi-plus bwi-fw"></i>
|
||||
{{ "addEmergencyContact" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<bit-table *ngIf="trustedContacts && trustedContacts.length">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell>{{ "name" | i18n }}</th>
|
||||
<th bitCell>{{ "accessLevel" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "options" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let c of trustedContacts; let i = index">
|
||||
<td bitCell class="tw-flex tw-items-center tw-gap-4">
|
||||
<bit-avatar
|
||||
[text]="c | userName"
|
||||
[id]="c.granteeId"
|
||||
[color]="c.avatarColor"
|
||||
size="small"
|
||||
></bit-avatar>
|
||||
<span>
|
||||
<a bitLink href="#" appStopClick (click)="edit(c)">{{ c.email }}</a>
|
||||
<span
|
||||
bitBadge
|
||||
variant="secondary"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
||||
>{{ "invited" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
>{{ "accepted" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||
>
|
||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.RecoveryApproved">{{
|
||||
"emergencyAccessRecoveryApproved" | i18n
|
||||
}}</span>
|
||||
|
||||
<table
|
||||
class="table table-hover table-list mb-0"
|
||||
*ngIf="trustedContacts && trustedContacts.length"
|
||||
>
|
||||
<tbody>
|
||||
<tr *ngFor="let c of trustedContacts; let i = index">
|
||||
<td width="30">
|
||||
<bit-avatar
|
||||
[text]="c | userName"
|
||||
[id]="c.granteeId"
|
||||
[color]="c.avatarColor"
|
||||
size="small"
|
||||
></bit-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" appStopClick (click)="edit(c)">{{ c.email }}</a>
|
||||
<span
|
||||
bitBadge
|
||||
variant="secondary"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
||||
>{{ "invited" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
>{{ "accepted" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||
>
|
||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.RecoveryApproved">{{
|
||||
"emergencyAccessRecoveryApproved" | i18n
|
||||
}}</span>
|
||||
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
"takeover" | i18n
|
||||
}}</span>
|
||||
|
||||
<small class="text-muted d-block" *ngIf="c.name">{{ c.name }}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<button
|
||||
[bitMenuTriggerFor]="trustedContactOptions"
|
||||
class="tw-border-none tw-bg-transparent tw-text-main"
|
||||
type="button"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #trustedContactOptions>
|
||||
<small class="tw-text-muted tw-block" *ngIf="c.name">{{ c.name }}</small>
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
"takeover" | i18n
|
||||
}}</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<button
|
||||
[bitMenuTriggerFor]="trustedContactOptions"
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
||||
(click)="reinvite(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "resendInvitation" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
(click)="confirm(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "confirm" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
(click)="approve(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "approve" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryInitiated ||
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved
|
||||
"
|
||||
(click)="reject(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "reject" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(c)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ng-container *ngIf="!trustedContacts || !trustedContacts.length">
|
||||
<p *ngIf="loaded">{{ "noTrustedContacts" | i18n }}</p>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
buttonType="main"
|
||||
></button>
|
||||
<bit-menu #trustedContactOptions>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
||||
(click)="reinvite(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "resendInvitation" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
(click)="confirm(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "confirm" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
(click)="approve(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "approve" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryInitiated ||
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved
|
||||
"
|
||||
(click)="reject(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "reject" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(c)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
<ng-container *ngIf="!trustedContacts || !trustedContacts.length">
|
||||
<p bitTypography="body1" class="tw-mt-2" *ngIf="loaded">{{ "noTrustedContacts" | i18n }}</p>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</bit-section>
|
||||
|
||||
<div class="page-header spaced-header">
|
||||
<h2>{{ "designatedEmergencyContacts" | i18n }}</h2>
|
||||
</div>
|
||||
<bit-section>
|
||||
<h2 bitTypography="h2">{{ "designatedEmergencyContacts" | i18n }}</h2>
|
||||
|
||||
<table
|
||||
class="table table-hover table-list mb-0"
|
||||
*ngIf="grantedContacts && grantedContacts.length"
|
||||
>
|
||||
<tbody>
|
||||
<tr *ngFor="let c of grantedContacts; let i = index">
|
||||
<td width="30">
|
||||
<bit-avatar
|
||||
[text]="c | userName"
|
||||
[id]="c.grantorId"
|
||||
[color]="c.avatarColor"
|
||||
size="small"
|
||||
></bit-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ c.email }}</span>
|
||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.Invited">{{
|
||||
"invited" | i18n
|
||||
}}</span>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
>{{ "accepted" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
variant="success"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved"
|
||||
>{{ "emergencyAccessRecoveryApproved" | i18n }}</span
|
||||
>
|
||||
<bit-table *ngIf="grantedContacts && grantedContacts.length">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell>{{ "name" | i18n }}</th>
|
||||
<th bitCell>{{ "accessLevel" | i18n }}</th>
|
||||
<th bitCell class="tw-text-right">{{ "options" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let c of grantedContacts; let i = index">
|
||||
<td bitCell class="tw-flex tw-items-center tw-gap-4">
|
||||
<bit-avatar
|
||||
[text]="c | userName"
|
||||
[id]="c.grantorId"
|
||||
[color]="c.avatarColor"
|
||||
size="small"
|
||||
></bit-avatar>
|
||||
<span>
|
||||
<span>{{ c.email }}</span>
|
||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.Invited">{{
|
||||
"invited" | i18n
|
||||
}}</span>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
>{{ "accepted" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
variant="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
variant="success"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved"
|
||||
>{{ "emergencyAccessRecoveryApproved" | i18n }}</span
|
||||
>
|
||||
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
"takeover" | i18n
|
||||
}}</span>
|
||||
|
||||
<small class="text-muted d-block" *ngIf="c.name">{{ c.name }}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<button
|
||||
[bitMenuTriggerFor]="grantedContactOptions"
|
||||
class="tw-border-none tw-bg-transparent tw-text-main"
|
||||
type="button"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #grantedContactOptions>
|
||||
<small class="tw-text-muted tw-block" *ngIf="c.name">{{ c.name }}</small>
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
"takeover" | i18n
|
||||
}}</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">
|
||||
<button
|
||||
[bitMenuTriggerFor]="grantedContactOptions"
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.Confirmed"
|
||||
(click)="requestAccess(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "requestAccess" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
||||
c.type === emergencyAccessType.Takeover
|
||||
"
|
||||
(click)="takeover(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
||||
{{ "takeover" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
||||
c.type === emergencyAccessType.View
|
||||
"
|
||||
[routerLink]="c.id"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-eye" aria-hidden="true"></i>
|
||||
{{ "view" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(c)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ng-container *ngIf="!grantedContacts || !grantedContacts.length">
|
||||
<p *ngIf="loaded">{{ "noGrantedAccess" | i18n }}</p>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
buttonType="main"
|
||||
></button>
|
||||
<bit-menu #grantedContactOptions>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="c.status === emergencyAccessStatusType.Confirmed"
|
||||
(click)="requestAccess(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||
{{ "requestAccess" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
||||
c.type === emergencyAccessType.Takeover
|
||||
"
|
||||
(click)="takeover(c)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
||||
{{ "takeover" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
*ngIf="
|
||||
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
||||
c.type === emergencyAccessType.View
|
||||
"
|
||||
[routerLink]="c.id"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-eye" aria-hidden="true"></i>
|
||||
{{ "view" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="remove(c)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
<ng-container *ngIf="!grantedContacts || !grantedContacts.length">
|
||||
<p bitTypography="body1" *ngIf="loaded">{{ "noGrantedAccess" | i18n }}</p>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</bit-section>
|
||||
</bit-container>
|
||||
|
||||
<ng-template #addEdit></ng-template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
|
||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -18,9 +18,18 @@ import {
|
||||
GrantorEmergencyAccess,
|
||||
} from "../../emergency-access/models/emergency-access";
|
||||
|
||||
import { EmergencyAccessConfirmComponent } from "./confirm/emergency-access-confirm.component";
|
||||
import { EmergencyAccessAddEditComponent } from "./emergency-access-add-edit.component";
|
||||
import { EmergencyAccessTakeoverComponent } from "./takeover/emergency-access-takeover.component";
|
||||
import {
|
||||
EmergencyAccessConfirmComponent,
|
||||
EmergencyAccessConfirmDialogResult,
|
||||
} from "./confirm/emergency-access-confirm.component";
|
||||
import {
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAddEditDialogResult,
|
||||
} from "./emergency-access-add-edit.component";
|
||||
import {
|
||||
EmergencyAccessTakeoverComponent,
|
||||
EmergencyAccessTakeoverResultType,
|
||||
} from "./takeover/emergency-access-takeover.component";
|
||||
|
||||
@Component({
|
||||
selector: "emergency-access",
|
||||
@@ -46,7 +55,6 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
constructor(
|
||||
private emergencyAccessService: EmergencyAccessService,
|
||||
private i18nService: I18nService,
|
||||
private modalService: ModalService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private messagingService: MessagingService,
|
||||
private userNamePipe: UserNamePipe,
|
||||
@@ -78,37 +86,26 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
async edit(details: GranteeEmergencyAccess) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
EmergencyAccessAddEditComponent,
|
||||
this.addEditModalRef,
|
||||
(comp) => {
|
||||
comp.name = this.userNamePipe.transform(details);
|
||||
comp.emergencyAccessId = details?.id;
|
||||
comp.readOnly = !this.canAccessPremium;
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
comp.onSaved.subscribe(() => {
|
||||
modal.close();
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.load();
|
||||
});
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
comp.onDeleted.subscribe(() => {
|
||||
modal.close();
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.remove(details);
|
||||
});
|
||||
edit = async (details: GranteeEmergencyAccess) => {
|
||||
const dialogRef = EmergencyAccessAddEditComponent.open(this.dialogService, {
|
||||
data: {
|
||||
name: this.userNamePipe.transform(details),
|
||||
emergencyAccessId: details?.id,
|
||||
readOnly: !this.canAccessPremium,
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
invite() {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.edit(null);
|
||||
}
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === EmergencyAccessAddEditDialogResult.Saved) {
|
||||
await this.load();
|
||||
} else if (result === EmergencyAccessAddEditDialogResult.Deleted) {
|
||||
await this.remove(details);
|
||||
}
|
||||
};
|
||||
|
||||
invite = async () => {
|
||||
await this.edit(null);
|
||||
};
|
||||
|
||||
async reinvite(contact: GranteeEmergencyAccess) {
|
||||
if (this.actionPromise != null) {
|
||||
@@ -135,29 +132,23 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
|
||||
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
|
||||
if (autoConfirm == null || !autoConfirm) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
EmergencyAccessConfirmComponent,
|
||||
this.confirmModalRef,
|
||||
(comp) => {
|
||||
comp.name = this.userNamePipe.transform(contact);
|
||||
comp.emergencyAccessId = contact.id;
|
||||
comp.userId = contact?.granteeId;
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
comp.onConfirmed.subscribe(async () => {
|
||||
modal.close();
|
||||
|
||||
comp.formPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId);
|
||||
await comp.formPromise;
|
||||
|
||||
updateUser();
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(contact)),
|
||||
);
|
||||
});
|
||||
const dialogRef = EmergencyAccessConfirmComponent.open(this.dialogService, {
|
||||
data: {
|
||||
name: this.userNamePipe.transform(contact),
|
||||
emergencyAccessId: contact.id,
|
||||
userId: contact?.granteeId,
|
||||
},
|
||||
);
|
||||
});
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === EmergencyAccessConfirmDialogResult.Confirmed) {
|
||||
await this.emergencyAccessService.confirm(contact.id, contact.granteeId);
|
||||
updateUser();
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(contact)),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -267,27 +258,23 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
async takeover(details: GrantorEmergencyAccess) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
EmergencyAccessTakeoverComponent,
|
||||
this.takeoverModalRef,
|
||||
(comp) => {
|
||||
comp.name = this.userNamePipe.transform(details);
|
||||
comp.email = details.email;
|
||||
comp.emergencyAccessId = details != null ? details.id : null;
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
comp.onDone.subscribe(() => {
|
||||
modal.close();
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("passwordResetFor", this.userNamePipe.transform(details)),
|
||||
);
|
||||
});
|
||||
takeover = async (details: GrantorEmergencyAccess) => {
|
||||
const dialogRef = EmergencyAccessTakeoverComponent.open(this.dialogService, {
|
||||
data: {
|
||||
name: this.userNamePipe.transform(details),
|
||||
email: details.email,
|
||||
emergencyAccessId: details.id ?? null,
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === EmergencyAccessTakeoverResultType.Done) {
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("passwordResetFor", this.userNamePipe.transform(details)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private removeGrantee(details: GranteeEmergencyAccess) {
|
||||
const index = this.trustedContacts.indexOf(details);
|
||||
|
||||
@@ -1,79 +1,54 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
|
||||
<form
|
||||
class="modal-content"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title" id="userAddEditTitle">
|
||||
{{ "takeover" | i18n }}
|
||||
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
|
||||
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
|
||||
</auth-password-callout>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="masterPassword">{{ "newMasterPass" | i18n }}</label>
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="password"
|
||||
name="NewMasterPasswordHash"
|
||||
class="form-control mb-1"
|
||||
[(ngModel)]="masterPassword"
|
||||
required
|
||||
appInputVerbatim
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
<app-password-strength
|
||||
[password]="masterPassword"
|
||||
[email]="email"
|
||||
[showText]="true"
|
||||
(passwordStrengthResult)="getStrengthResult($event)"
|
||||
>
|
||||
</app-password-strength>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
|
||||
<input
|
||||
id="masterPasswordRetype"
|
||||
type="password"
|
||||
name="MasterPasswordRetype"
|
||||
class="form-control"
|
||||
[(ngModel)]="masterPasswordRetype"
|
||||
required
|
||||
appInputVerbatim
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<form [formGroup]="takeoverForm" [bitSubmit]="submit">
|
||||
<bit-dialog dialogSize="large">
|
||||
<span bitDialogTitle>
|
||||
{{ "takeover" | i18n }}
|
||||
<small class="tw-text-muted" *ngIf="params.name">{{ params.name }}</small>
|
||||
</span>
|
||||
<div bitDialogContent>
|
||||
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
|
||||
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
|
||||
</auth-password-callout>
|
||||
<div class="tw-w-full tw-flex tw-gap-4">
|
||||
<div class="tw-relative tw-flex-1">
|
||||
<bit-form-field disableMargin class="tw-mb-2">
|
||||
<bit-label>{{ "newMasterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
formControlName="masterPassword"
|
||||
/>
|
||||
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
|
||||
</bit-form-field>
|
||||
<app-password-strength
|
||||
[password]="takeoverForm.value.masterPassword"
|
||||
[email]="email"
|
||||
[showText]="true"
|
||||
(passwordStrengthResult)="getStrengthResult($event)"
|
||||
>
|
||||
</app-password-strength>
|
||||
</div>
|
||||
<div class="tw-relative tw-flex-1">
|
||||
<bit-form-field disableMargin class="tw-mb-2">
|
||||
<bit-label>{{ "confirmNewMasterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
formControlName="masterPasswordRetype"
|
||||
/>
|
||||
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div bitDialogFooter>
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button bitButton bitFormButton type="button" buttonType="secondary" bitDialogClose>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, OnDestroy, OnInit, Inject, Input } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { takeUntil } from "rxjs";
|
||||
|
||||
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
|
||||
@@ -15,6 +17,17 @@ import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { EmergencyAccessService } from "../../../emergency-access";
|
||||
|
||||
export enum EmergencyAccessTakeoverResultType {
|
||||
Done = "done",
|
||||
}
|
||||
type EmergencyAccessTakeoverDialogData = {
|
||||
/** display name of the account requesting emergency access takeover */
|
||||
name: string;
|
||||
/** email of the account requesting emergency access takeover */
|
||||
email: string;
|
||||
/** traces a unique emergency request */
|
||||
emergencyAccessId: string;
|
||||
};
|
||||
@Component({
|
||||
selector: "emergency-access-takeover",
|
||||
templateUrl: "emergency-access-takeover.component.html",
|
||||
@@ -24,16 +37,16 @@ export class EmergencyAccessTakeoverComponent
|
||||
extends ChangePasswordComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
@Output() onDone = new EventEmitter();
|
||||
@Input() emergencyAccessId: string;
|
||||
@Input() name: string;
|
||||
@Input() email: string;
|
||||
@Input() kdf: KdfType;
|
||||
@Input() kdfIterations: number;
|
||||
|
||||
formPromise: Promise<any>;
|
||||
takeoverForm = this.formBuilder.group({
|
||||
masterPassword: ["", [Validators.required]],
|
||||
masterPasswordRetype: ["", [Validators.required]],
|
||||
});
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected params: EmergencyAccessTakeoverDialogData,
|
||||
private formBuilder: FormBuilder,
|
||||
i18nService: I18nService,
|
||||
cryptoService: CryptoService,
|
||||
messagingService: MessagingService,
|
||||
@@ -44,6 +57,7 @@ export class EmergencyAccessTakeoverComponent
|
||||
private emergencyAccessService: EmergencyAccessService,
|
||||
private logService: LogService,
|
||||
dialogService: DialogService,
|
||||
private dialogRef: DialogRef<EmergencyAccessTakeoverResultType>,
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
@@ -58,7 +72,9 @@ export class EmergencyAccessTakeoverComponent
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const policies = await this.emergencyAccessService.getGrantorPolicies(this.emergencyAccessId);
|
||||
const policies = await this.emergencyAccessService.getGrantorPolicies(
|
||||
this.params.emergencyAccessId,
|
||||
);
|
||||
this.policyService
|
||||
.masterPasswordPolicyOptions$(policies)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
@@ -70,18 +86,23 @@ export class EmergencyAccessTakeoverComponent
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
submit = async () => {
|
||||
if (this.takeoverForm.invalid) {
|
||||
this.takeoverForm.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
this.masterPassword = this.takeoverForm.get("masterPassword").value;
|
||||
this.masterPasswordRetype = this.takeoverForm.get("masterPasswordRetype").value;
|
||||
if (!(await this.strongPassword())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.emergencyAccessService.takeover(
|
||||
this.emergencyAccessId,
|
||||
this.params.emergencyAccessId,
|
||||
this.masterPassword,
|
||||
this.email,
|
||||
this.params.email,
|
||||
);
|
||||
this.onDone.emit();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
this.platformUtilsService.showToast(
|
||||
@@ -90,5 +111,20 @@ export class EmergencyAccessTakeoverComponent
|
||||
this.i18nService.t("unexpectedError"),
|
||||
);
|
||||
}
|
||||
}
|
||||
this.dialogRef.close(EmergencyAccessTakeoverResultType.Done);
|
||||
};
|
||||
/**
|
||||
* Strongly typed helper to open a EmergencyAccessTakeoverComponent
|
||||
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||
* @param config Configuration for the dialog
|
||||
*/
|
||||
static open = (
|
||||
dialogService: DialogService,
|
||||
config: DialogConfig<EmergencyAccessTakeoverDialogData>,
|
||||
) => {
|
||||
return dialogService.open<EmergencyAccessTakeoverResultType>(
|
||||
EmergencyAccessTakeoverComponent,
|
||||
config,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,71 +1,76 @@
|
||||
<div class="page-header">
|
||||
<h1>{{ "vault" | i18n }}</h1>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<h1 bitTypography="h1">{{ "vault" | i18n }}</h1>
|
||||
|
||||
<div class="tw-mt-6">
|
||||
<ng-container *ngIf="ciphers.length">
|
||||
<table class="table table-hover table-list table-ciphers">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of ciphers">
|
||||
<td class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
<bit-table>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let currentCipher of ciphers">
|
||||
<td bitCell>
|
||||
<app-vault-icon [cipher]="currentCipher"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
||||
c.name
|
||||
}}</a>
|
||||
<ng-container *ngIf="c.organizationId">
|
||||
<td bitCell class="tw-w-full">
|
||||
<a
|
||||
bitLink
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="selectCipher(currentCipher)"
|
||||
title="{{ 'editItem' | i18n }}"
|
||||
>{{ currentCipher.name }}</a
|
||||
>
|
||||
<ng-container *ngIf="currentCipher.organizationId">
|
||||
<i
|
||||
class="bwi bwi-collection"
|
||||
appStopProp
|
||||
title="{{ 'shared' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
<span class="tw-sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<ng-container *ngIf="currentCipher.hasAttachments">
|
||||
<i
|
||||
class="bwi bwi-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
<span class="tw-sr-only">{{ "attachments" | i18n }}</span>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small>{{ c.subTitle }}</small>
|
||||
<small class="tw-text-xs">{{ currentCipher.subTitle }}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown *ngIf="c.hasAttachments">
|
||||
<td bitCell>
|
||||
<div *ngIf="currentCipher.hasAttachments">
|
||||
<button
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
[bitMenuTriggerFor]="optionsMenu"
|
||||
type="button"
|
||||
id="dropdownMenuButton"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
buttonType="main"
|
||||
bitIconButton="bwi-ellipsis-v"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="viewAttachments(c)">
|
||||
></button>
|
||||
<bit-menu #optionsMenu>
|
||||
<button
|
||||
type="button"
|
||||
bitMenuItem
|
||||
appStopClick
|
||||
(click)="viewAttachments(currentCipher)"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-paperclip" aria-hidden="true"></i>
|
||||
{{ "attachments" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-template #cipherAddEdit></ng-template>
|
||||
|
||||
@@ -578,6 +578,9 @@
|
||||
"access": {
|
||||
"message": "Access"
|
||||
},
|
||||
"accessLevel": {
|
||||
"message": "Access level"
|
||||
},
|
||||
"loggedOut": {
|
||||
"message": "Logged out"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user