Merge branch 'main' into AC-2413-Migrate-policies-component
This commit is contained in:
commit
ee795ef8d6
|
@ -4,6 +4,7 @@ import remarkGfm from "remark-gfm";
|
|||
|
||||
const config: StorybookConfig = {
|
||||
stories: [
|
||||
"../libs/auth/src/**/*.mdx",
|
||||
"../libs/auth/src/**/*.stories.@(js|jsx|ts|tsx)",
|
||||
"../libs/components/src/**/*.mdx",
|
||||
"../libs/components/src/**/*.stories.@(js|jsx|ts|tsx)",
|
||||
|
|
10
angular.json
10
angular.json
|
@ -142,7 +142,15 @@
|
|||
"configDir": ".storybook",
|
||||
"browserTarget": "components:build",
|
||||
"compodoc": true,
|
||||
"compodocArgs": ["-p", "./tsconfig.json", "-e", "json", "-d", "."],
|
||||
"compodocArgs": [
|
||||
"-p",
|
||||
"./tsconfig.json",
|
||||
"-e",
|
||||
"json",
|
||||
"-d",
|
||||
".",
|
||||
"--disableRoutesGraph"
|
||||
],
|
||||
"port": 6006
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"dev_flags": {},
|
||||
"devFlags": {},
|
||||
"flags": {
|
||||
"showPasswordless": true,
|
||||
"enableCipherKeyEncryption": false,
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
"devFlags": {
|
||||
"managedEnvironment": {
|
||||
"base": "https://localhost:8080"
|
||||
}
|
||||
},
|
||||
"skipWelcomeOnInstall": true
|
||||
},
|
||||
"flags": {
|
||||
"showPasswordless": true,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@bitwarden/browser",
|
||||
"version": "2024.4.2",
|
||||
"version": "2024.5.0",
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"build:mv3": "cross-env MANIFEST_VERSION=3 webpack",
|
||||
|
|
|
@ -374,12 +374,21 @@
|
|||
"other": {
|
||||
"message": "Other"
|
||||
},
|
||||
"unlockMethods": {
|
||||
"message": "Unlock options"
|
||||
},
|
||||
"unlockMethodNeededToChangeTimeoutActionDesc": {
|
||||
"message": "Set up an unlock method to change your vault timeout action."
|
||||
},
|
||||
"unlockMethodNeeded": {
|
||||
"message": "Set up an unlock method in Settings"
|
||||
},
|
||||
"sessionTimeoutHeader": {
|
||||
"message": "Session timeout"
|
||||
},
|
||||
"otherOptions": {
|
||||
"message": "Other options"
|
||||
},
|
||||
"rateExtension": {
|
||||
"message": "Rate the extension"
|
||||
},
|
||||
|
@ -3023,6 +3032,15 @@
|
|||
"adminConsole": {
|
||||
"message": "Admin Console"
|
||||
},
|
||||
"accountSecurity": {
|
||||
"message": "Account security"
|
||||
},
|
||||
"notifications": {
|
||||
"message": "Notifications"
|
||||
},
|
||||
"appearance": {
|
||||
"message": "Appearance"
|
||||
},
|
||||
"errorAssigningTargetCollection": {
|
||||
"message": "Error assigning target collection."
|
||||
},
|
||||
|
|
|
@ -226,7 +226,7 @@
|
|||
"message": "Help & feedback"
|
||||
},
|
||||
"helpCenter": {
|
||||
"message": "Bitwarden Help center"
|
||||
"message": "Bitwarden Help centre"
|
||||
},
|
||||
"communityForums": {
|
||||
"message": "Explore Bitwarden community forums"
|
||||
|
@ -728,7 +728,7 @@
|
|||
"message": "Change the application's colour theme."
|
||||
},
|
||||
"themeDescAlt": {
|
||||
"message": "Change the application's color theme. Applies to all logged in accounts."
|
||||
"message": "Change the application's colour theme. Applies to all logged in accounts."
|
||||
},
|
||||
"dark": {
|
||||
"message": "Dark",
|
||||
|
@ -1165,7 +1165,7 @@
|
|||
"message": "Show a recognizable image next to each login."
|
||||
},
|
||||
"faviconDescAlt": {
|
||||
"message": "Show a recognizable image next to each login. Applies to all logged in accounts."
|
||||
"message": "Show a recognisable image next to each login. Applies to all logged in accounts."
|
||||
},
|
||||
"enableBadgeCounter": {
|
||||
"message": "Show badge counter"
|
||||
|
@ -1730,7 +1730,7 @@
|
|||
"message": "An organization policy is affecting your ownership options."
|
||||
},
|
||||
"personalOwnershipPolicyInEffectImports": {
|
||||
"message": "An organization policy has blocked importing items into your individual vault."
|
||||
"message": "An organisation policy has blocked importing items into your individual vault."
|
||||
},
|
||||
"excludedDomains": {
|
||||
"message": "Excluded Domains"
|
||||
|
@ -1990,7 +1990,7 @@
|
|||
"message": "Your Master Password was recently changed by an administrator in your organization. In order to access the vault, you must update it now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
|
||||
},
|
||||
"updateWeakMasterPasswordWarning": {
|
||||
"message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
|
||||
"message": "Your master password does not meet one or more of your organisation policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
|
||||
},
|
||||
"resetPasswordPolicyAutoEnroll": {
|
||||
"message": "Automatic Enrollment"
|
||||
|
@ -2006,11 +2006,11 @@
|
|||
"description": "Used as a message within the notification bar when no folders are found"
|
||||
},
|
||||
"orgPermissionsUpdatedMustSetPassword": {
|
||||
"message": "Your organization permissions were updated, requiring you to set a master password.",
|
||||
"message": "Your organisation permissions were updated, requiring you to set a master password.",
|
||||
"description": "Used as a card title description on the set password page to explain why the user is there"
|
||||
},
|
||||
"orgRequiresYouToSetPassword": {
|
||||
"message": "Your organization requires you to set a master password.",
|
||||
"message": "Your organisation requires you to set a master password.",
|
||||
"description": "Used as a card title description on the set password page to explain why the user is there"
|
||||
},
|
||||
"verificationRequired": {
|
||||
|
@ -2037,7 +2037,7 @@
|
|||
}
|
||||
},
|
||||
"vaultTimeoutPolicyWithActionInEffect": {
|
||||
"message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.",
|
||||
"message": "Your organisation policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
|
@ -2054,7 +2054,7 @@
|
|||
}
|
||||
},
|
||||
"vaultTimeoutActionPolicyInEffect": {
|
||||
"message": "Your organization policies have set your vault timeout action to $ACTION$.",
|
||||
"message": "Your organisation policies have set your vault timeout action to $ACTION$.",
|
||||
"placeholders": {
|
||||
"action": {
|
||||
"content": "$1",
|
||||
|
@ -2111,7 +2111,7 @@
|
|||
"message": "Exporting Personal Vault"
|
||||
},
|
||||
"exportingIndividualVaultDescription": {
|
||||
"message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.",
|
||||
"message": "Only the individual vault items associated with $EMAIL$ will be exported. Organisation vault items will not be included. Only vault item information will be exported and will not include associated attachments.",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"content": "$1",
|
||||
|
@ -2305,7 +2305,7 @@
|
|||
}
|
||||
},
|
||||
"autofillPageLoadPolicyActivated": {
|
||||
"message": "Your organization policies have turned on auto-fill on page load."
|
||||
"message": "Your organisation policies have turned on auto-fill on page load."
|
||||
},
|
||||
"howToAutofill": {
|
||||
"message": "How to auto-fill"
|
||||
|
@ -2377,7 +2377,7 @@
|
|||
"message": "Approve with master password"
|
||||
},
|
||||
"ssoIdentifierRequired": {
|
||||
"message": "Organization SSO identifier is required."
|
||||
"message": "Organisation SSO identifier is required."
|
||||
},
|
||||
"eu": {
|
||||
"message": "EU",
|
||||
|
@ -2688,7 +2688,7 @@
|
|||
"message": "Total"
|
||||
},
|
||||
"importWarning": {
|
||||
"message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?",
|
||||
"message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organisation. Do you want to proceed?",
|
||||
"placeholders": {
|
||||
"organization": {
|
||||
"content": "$1",
|
||||
|
@ -3007,10 +3007,10 @@
|
|||
"message": "Passkey removed"
|
||||
},
|
||||
"unassignedItemsBannerNotice": {
|
||||
"message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
||||
"message": "Notice: Unassigned organisation items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
||||
},
|
||||
"unassignedItemsBannerSelfHostNotice": {
|
||||
"message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console."
|
||||
"message": "Notice: On May 16, 2024, unassigned organisation items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console."
|
||||
},
|
||||
"unassignedItemsBannerCTAPartOne": {
|
||||
"message": "Assign these items to a collection from the",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"message": "Bitwarden"
|
||||
},
|
||||
"extName": {
|
||||
"message": "Bitwarden Password Manager",
|
||||
"message": "Bitwarden - Administrador de contraseñas",
|
||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||
},
|
||||
"extDesc": {
|
||||
|
@ -2962,27 +2962,27 @@
|
|||
"description": "Label indicating the most common import formats"
|
||||
},
|
||||
"overrideDefaultBrowserAutofillTitle": {
|
||||
"message": "¿Quiere hacer de Bitwarden su gestor de contraseñas predeterminado?",
|
||||
"message": "¿Hacer de Bitwarden su administrador de contraseñas predeterminado?",
|
||||
"description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior"
|
||||
},
|
||||
"overrideDefaultBrowserAutofillDescription": {
|
||||
"message": "Pasar por alto esta opción puede causar conflictos entre el menú de relleno automático de Bitwarden y el del navegador.",
|
||||
"message": "Pasar por alto esta opción puede causar conflictos entre el menú de autocompletar de Bitwarden y el de tu navegador.",
|
||||
"description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior"
|
||||
},
|
||||
"overrideDefaultBrowserAutoFillSettings": {
|
||||
"message": "Hacer de Bitwarden su gestor de contraseñas predeterminado",
|
||||
"message": "Hacer de Bitwarden tu administrador de contraseñas predeterminado",
|
||||
"description": "Label for the setting that allows overriding the default browser autofill settings"
|
||||
},
|
||||
"privacyPermissionAdditionNotGrantedTitle": {
|
||||
"message": "No se pudo establecer Bitwarden como el gestor de contraseñas predeterminado",
|
||||
"message": "No se puede establecer Bitwarden como el administrador de contraseñas predeterminado",
|
||||
"description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings"
|
||||
},
|
||||
"privacyPermissionAdditionNotGrantedDescription": {
|
||||
"message": "Debe otorgar los permisos de privacidad del navegador a Bitwarden para establecerlo como gestor de contraseñas predeterminado.",
|
||||
"message": "Debes otorgar permisos de privacidad del navegador a Bitwarden para establecerlo como administrador de contraseñas predeterminado.",
|
||||
"description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings"
|
||||
},
|
||||
"makeDefault": {
|
||||
"message": "Predeterminar",
|
||||
"message": "Establecer como predeterminado",
|
||||
"description": "Button text for the setting that allows overriding the default browser autofill settings"
|
||||
},
|
||||
"saveCipherAttemptSuccess": {
|
||||
|
@ -2998,7 +2998,7 @@
|
|||
"description": "Notification message for when saving credentials has failed."
|
||||
},
|
||||
"success": {
|
||||
"message": "Success"
|
||||
"message": "Éxito"
|
||||
},
|
||||
"removePasskey": {
|
||||
"message": "Eliminar passkey"
|
||||
|
@ -3013,20 +3013,20 @@
|
|||
"message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console."
|
||||
},
|
||||
"unassignedItemsBannerCTAPartOne": {
|
||||
"message": "Assign these items to a collection from the",
|
||||
"message": "Asignar estos elementos a una colección de",
|
||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"unassignedItemsBannerCTAPartTwo": {
|
||||
"message": "to make them visible.",
|
||||
"message": "para hcerlos visibles.",
|
||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"adminConsole": {
|
||||
"message": "Admin Console"
|
||||
"message": "Consola de administrador"
|
||||
},
|
||||
"errorAssigningTargetCollection": {
|
||||
"message": "Error assigning target collection."
|
||||
"message": "Error al asignar la colección de destino."
|
||||
},
|
||||
"errorAssigningTargetFolder": {
|
||||
"message": "Error assigning target folder."
|
||||
"message": "Error al asignar la carpeta de destino."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,10 +173,10 @@
|
|||
"message": "Keisti pagrindinį slaptažodį"
|
||||
},
|
||||
"continueToWebApp": {
|
||||
"message": "Continue to web app?"
|
||||
"message": "Tęsti į žiniatinklio programėlę?"
|
||||
},
|
||||
"changeMasterPasswordOnWebConfirmation": {
|
||||
"message": "You can change your master password on the Bitwarden web app."
|
||||
"message": "Pagrindinį slaptažodį galite pakeisti „Bitwarden“ žiniatinklio programėlėje."
|
||||
},
|
||||
"fingerprintPhrase": {
|
||||
"message": "Pirštų atspaudų frazė",
|
||||
|
@ -2998,7 +2998,7 @@
|
|||
"description": "Notification message for when saving credentials has failed."
|
||||
},
|
||||
"success": {
|
||||
"message": "Success"
|
||||
"message": "Sėkmė"
|
||||
},
|
||||
"removePasskey": {
|
||||
"message": "Pašalinti slaptaraktį"
|
||||
|
@ -3007,26 +3007,26 @@
|
|||
"message": "Pašalintas slaptaraktis"
|
||||
},
|
||||
"unassignedItemsBannerNotice": {
|
||||
"message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
||||
"message": "Pranešimas: nepriskirti organizacijos elementai nebėra matomi peržiūros rodinyje Visi saugyklos ir yra pasiekiami tik per Administratoriaus konsolę."
|
||||
},
|
||||
"unassignedItemsBannerSelfHostNotice": {
|
||||
"message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console."
|
||||
"message": "Pranešimas: 2024 m. gegužės 16 d. nepriskirti organizacijos elementai nebėra matomi peržiūros rodinyje Visi saugyklos ir yra pasiekiami tik per Administratoriaus konsolę."
|
||||
},
|
||||
"unassignedItemsBannerCTAPartOne": {
|
||||
"message": "Assign these items to a collection from the",
|
||||
"message": "Priskirkite šiuos elementus kolekcijai iš",
|
||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"unassignedItemsBannerCTAPartTwo": {
|
||||
"message": "to make them visible.",
|
||||
"message": ", kad jie būtų matomi.",
|
||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||
},
|
||||
"adminConsole": {
|
||||
"message": "Admin Console"
|
||||
"message": "Administratoriaus konsolės"
|
||||
},
|
||||
"errorAssigningTargetCollection": {
|
||||
"message": "Error assigning target collection."
|
||||
"message": "Klaida priskiriant tikslinę kolekciją."
|
||||
},
|
||||
"errorAssigningTargetFolder": {
|
||||
"message": "Error assigning target folder."
|
||||
"message": "Klaida priskiriant tikslinį aplanką."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
KeyGenerationServiceInitOptions,
|
||||
keyGenerationServiceFactory,
|
||||
} from "../../../platform/background/service-factories/key-generation-service.factory";
|
||||
import { logServiceFactory } from "../../../platform/background/service-factories/log-service.factory";
|
||||
import {
|
||||
PlatformUtilsServiceInitOptions,
|
||||
platformUtilsServiceFactory,
|
||||
|
@ -88,6 +89,7 @@ export function deviceTrustServiceFactory(
|
|||
await stateProviderFactory(cache, opts),
|
||||
await secureStorageServiceFactory(cache, opts),
|
||||
await userDecryptionOptionsServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,20 +4,35 @@ import {
|
|||
} from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service";
|
||||
|
||||
import {
|
||||
encryptServiceFactory,
|
||||
EncryptServiceInitOptions,
|
||||
} from "../../../platform/background/service-factories/encrypt-service.factory";
|
||||
import {
|
||||
CachedServices,
|
||||
factory,
|
||||
FactoryOptions,
|
||||
} from "../../../platform/background/service-factories/factory-options";
|
||||
import {
|
||||
keyGenerationServiceFactory,
|
||||
KeyGenerationServiceInitOptions,
|
||||
} from "../../../platform/background/service-factories/key-generation-service.factory";
|
||||
import {
|
||||
stateProviderFactory,
|
||||
StateProviderInitOptions,
|
||||
} from "../../../platform/background/service-factories/state-provider.factory";
|
||||
import {
|
||||
stateServiceFactory,
|
||||
StateServiceInitOptions,
|
||||
} from "../../../platform/background/service-factories/state-service.factory";
|
||||
|
||||
type MasterPasswordServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type MasterPasswordServiceInitOptions = MasterPasswordServiceFactoryOptions &
|
||||
StateProviderInitOptions;
|
||||
StateProviderInitOptions &
|
||||
StateServiceInitOptions &
|
||||
KeyGenerationServiceInitOptions &
|
||||
EncryptServiceInitOptions;
|
||||
|
||||
export function internalMasterPasswordServiceFactory(
|
||||
cache: { masterPasswordService?: InternalMasterPasswordServiceAbstraction } & CachedServices,
|
||||
|
@ -27,7 +42,13 @@ export function internalMasterPasswordServiceFactory(
|
|||
cache,
|
||||
"masterPasswordService",
|
||||
opts,
|
||||
async () => new MasterPasswordService(await stateProviderFactory(cache, opts)),
|
||||
async () =>
|
||||
new MasterPasswordService(
|
||||
await stateProviderFactory(cache, opts),
|
||||
await stateServiceFactory(cache, opts),
|
||||
await keyGenerationServiceFactory(cache, opts),
|
||||
await encryptServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
import { PinCryptoServiceAbstraction, PinCryptoService } from "@bitwarden/auth/common";
|
||||
|
||||
import {
|
||||
VaultTimeoutSettingsServiceInitOptions,
|
||||
vaultTimeoutSettingsServiceFactory,
|
||||
} from "../../../background/service-factories/vault-timeout-settings-service.factory";
|
||||
import {
|
||||
CryptoServiceInitOptions,
|
||||
cryptoServiceFactory,
|
||||
} from "../../../platform/background/service-factories/crypto-service.factory";
|
||||
import {
|
||||
FactoryOptions,
|
||||
CachedServices,
|
||||
factory,
|
||||
} from "../../../platform/background/service-factories/factory-options";
|
||||
import {
|
||||
LogServiceInitOptions,
|
||||
logServiceFactory,
|
||||
} from "../../../platform/background/service-factories/log-service.factory";
|
||||
import {
|
||||
StateServiceInitOptions,
|
||||
stateServiceFactory,
|
||||
} from "../../../platform/background/service-factories/state-service.factory";
|
||||
|
||||
import { KdfConfigServiceInitOptions, kdfConfigServiceFactory } from "./kdf-config-service.factory";
|
||||
|
||||
type PinCryptoServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type PinCryptoServiceInitOptions = PinCryptoServiceFactoryOptions &
|
||||
StateServiceInitOptions &
|
||||
CryptoServiceInitOptions &
|
||||
VaultTimeoutSettingsServiceInitOptions &
|
||||
LogServiceInitOptions &
|
||||
KdfConfigServiceInitOptions;
|
||||
|
||||
export function pinCryptoServiceFactory(
|
||||
cache: { pinCryptoService?: PinCryptoServiceAbstraction } & CachedServices,
|
||||
opts: PinCryptoServiceInitOptions,
|
||||
): Promise<PinCryptoServiceAbstraction> {
|
||||
return factory(
|
||||
cache,
|
||||
"pinCryptoService",
|
||||
opts,
|
||||
async () =>
|
||||
new PinCryptoService(
|
||||
await stateServiceFactory(cache, opts),
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await vaultTimeoutSettingsServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts),
|
||||
await kdfConfigServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import { PinServiceAbstraction, PinService } from "@bitwarden/auth/common";
|
||||
|
||||
import {
|
||||
CryptoFunctionServiceInitOptions,
|
||||
cryptoFunctionServiceFactory,
|
||||
} from "../../../platform/background/service-factories/crypto-function-service.factory";
|
||||
import {
|
||||
EncryptServiceInitOptions,
|
||||
encryptServiceFactory,
|
||||
} from "../../../platform/background/service-factories/encrypt-service.factory";
|
||||
import {
|
||||
FactoryOptions,
|
||||
CachedServices,
|
||||
factory,
|
||||
} from "../../../platform/background/service-factories/factory-options";
|
||||
import {
|
||||
KeyGenerationServiceInitOptions,
|
||||
keyGenerationServiceFactory,
|
||||
} from "../../../platform/background/service-factories/key-generation-service.factory";
|
||||
import {
|
||||
LogServiceInitOptions,
|
||||
logServiceFactory,
|
||||
} from "../../../platform/background/service-factories/log-service.factory";
|
||||
import {
|
||||
StateProviderInitOptions,
|
||||
stateProviderFactory,
|
||||
} from "../../../platform/background/service-factories/state-provider.factory";
|
||||
import {
|
||||
StateServiceInitOptions,
|
||||
stateServiceFactory,
|
||||
} from "../../../platform/background/service-factories/state-service.factory";
|
||||
|
||||
import { AccountServiceInitOptions, accountServiceFactory } from "./account-service.factory";
|
||||
import { KdfConfigServiceInitOptions, kdfConfigServiceFactory } from "./kdf-config-service.factory";
|
||||
import {
|
||||
MasterPasswordServiceInitOptions,
|
||||
masterPasswordServiceFactory,
|
||||
} from "./master-password-service.factory";
|
||||
|
||||
type PinServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type PinServiceInitOptions = PinServiceFactoryOptions &
|
||||
AccountServiceInitOptions &
|
||||
CryptoFunctionServiceInitOptions &
|
||||
EncryptServiceInitOptions &
|
||||
KdfConfigServiceInitOptions &
|
||||
KeyGenerationServiceInitOptions &
|
||||
LogServiceInitOptions &
|
||||
MasterPasswordServiceInitOptions &
|
||||
StateProviderInitOptions &
|
||||
StateServiceInitOptions;
|
||||
|
||||
export function pinServiceFactory(
|
||||
cache: { pinService?: PinServiceAbstraction } & CachedServices,
|
||||
opts: PinServiceInitOptions,
|
||||
): Promise<PinServiceAbstraction> {
|
||||
return factory(
|
||||
cache,
|
||||
"pinService",
|
||||
opts,
|
||||
async () =>
|
||||
new PinService(
|
||||
await accountServiceFactory(cache, opts),
|
||||
await cryptoFunctionServiceFactory(cache, opts),
|
||||
await encryptServiceFactory(cache, opts),
|
||||
await kdfConfigServiceFactory(cache, opts),
|
||||
await keyGenerationServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts),
|
||||
await masterPasswordServiceFactory(cache, opts),
|
||||
await stateProviderFactory(cache, opts),
|
||||
await stateServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -37,7 +37,7 @@ import {
|
|||
internalMasterPasswordServiceFactory,
|
||||
MasterPasswordServiceInitOptions,
|
||||
} from "./master-password-service.factory";
|
||||
import { PinCryptoServiceInitOptions, pinCryptoServiceFactory } from "./pin-crypto-service.factory";
|
||||
import { PinServiceInitOptions, pinServiceFactory } from "./pin-service.factory";
|
||||
import {
|
||||
userDecryptionOptionsServiceFactory,
|
||||
UserDecryptionOptionsServiceInitOptions,
|
||||
|
@ -57,7 +57,7 @@ export type UserVerificationServiceInitOptions = UserVerificationServiceFactoryO
|
|||
I18nServiceInitOptions &
|
||||
UserVerificationApiServiceInitOptions &
|
||||
UserDecryptionOptionsServiceInitOptions &
|
||||
PinCryptoServiceInitOptions &
|
||||
PinServiceInitOptions &
|
||||
LogServiceInitOptions &
|
||||
VaultTimeoutSettingsServiceInitOptions &
|
||||
PlatformUtilsServiceInitOptions &
|
||||
|
@ -80,7 +80,7 @@ export function userVerificationServiceFactory(
|
|||
await i18nServiceFactory(cache, opts),
|
||||
await userVerificationApiServiceFactory(cache, opts),
|
||||
await userDecryptionOptionsServiceFactory(cache, opts),
|
||||
await pinCryptoServiceFactory(cache, opts),
|
||||
await pinServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts),
|
||||
await vaultTimeoutSettingsServiceFactory(cache, opts),
|
||||
await platformUtilsServiceFactory(cache, opts),
|
||||
|
|
|
@ -12,8 +12,16 @@
|
|||
<input class="tw-font-mono" bitInput type="password" formControlName="pin" />
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
</bit-form-field>
|
||||
<label class="tw-flex tw-items-start tw-gap-2" *ngIf="showMasterPassOnRestart">
|
||||
<input class="tw-mt-1" type="checkbox" bitCheckbox formControlName="masterPassOnRestart" />
|
||||
<label
|
||||
class="tw-flex tw-items-start tw-gap-2"
|
||||
*ngIf="showMasterPasswordOnClientRestartOption"
|
||||
>
|
||||
<input
|
||||
class="tw-mt-1"
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
formControlName="requireMasterPasswordOnClientRestart"
|
||||
/>
|
||||
<span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Router } from "@angular/router";
|
|||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
|
||||
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
|
@ -63,7 +63,7 @@ export class LockComponent extends BaseLockComponent {
|
|||
dialogService: DialogService,
|
||||
deviceTrustService: DeviceTrustServiceAbstraction,
|
||||
userVerificationService: UserVerificationService,
|
||||
pinCryptoService: PinCryptoServiceAbstraction,
|
||||
pinService: PinServiceAbstraction,
|
||||
private routerService: BrowserRouterService,
|
||||
biometricStateService: BiometricStateService,
|
||||
accountService: AccountService,
|
||||
|
@ -89,7 +89,7 @@ export class LockComponent extends BaseLockComponent {
|
|||
dialogService,
|
||||
deviceTrustService,
|
||||
userVerificationService,
|
||||
pinCryptoService,
|
||||
pinService,
|
||||
biometricStateService,
|
||||
accountService,
|
||||
authService,
|
||||
|
@ -143,15 +143,17 @@ export class LockComponent extends BaseLockComponent {
|
|||
try {
|
||||
success = await super.unlockBiometric();
|
||||
} catch (e) {
|
||||
const error = BiometricErrors[e as BiometricErrorTypes];
|
||||
const error = BiometricErrors[e?.message as BiometricErrorTypes];
|
||||
|
||||
if (error == null) {
|
||||
this.logService.error("Unknown error: " + e);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.biometricError = this.i18nService.t(error.description);
|
||||
} finally {
|
||||
this.pendingBiometric = false;
|
||||
}
|
||||
this.pendingBiometric = false;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
<app-header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "accountSecurity" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right">
|
||||
<app-pop-out></app-pop-out>
|
||||
</div>
|
||||
</app-header>
|
||||
<main tabindex="-1" [formGroup]="form">
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "unlockMethods" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="supportsBiometric">
|
||||
<label for="biometric">{{ "unlockWithBiometrics" | i18n }}</label>
|
||||
<input id="biometric" type="checkbox" formControlName="biometric" />
|
||||
</div>
|
||||
<div
|
||||
class="box-content-row box-content-row-checkbox"
|
||||
appBoxRow
|
||||
*ngIf="supportsBiometric && this.form.value.biometric"
|
||||
>
|
||||
<label for="autoBiometricsPrompt">{{ "enableAutoBiometricsPrompt" | i18n }}</label>
|
||||
<input
|
||||
id="autoBiometricsPrompt"
|
||||
type="checkbox"
|
||||
(change)="updateAutoBiometricsPrompt()"
|
||||
formControlName="enableAutoBiometricsPrompt"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="pin">{{ "unlockWithPin" | i18n }}</label>
|
||||
<input id="pin" type="checkbox" formControlName="pin" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "sessionTimeoutHeader" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
|
||||
<span *ngIf="policy.timeout && policy.action">
|
||||
{{
|
||||
"vaultTimeoutPolicyWithActionInEffect"
|
||||
| i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
|
||||
}}
|
||||
</span>
|
||||
<span *ngIf="policy.timeout && !policy.action">
|
||||
{{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }}
|
||||
</span>
|
||||
<span *ngIf="!policy.timeout && policy.action">
|
||||
{{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
|
||||
</span>
|
||||
</app-callout>
|
||||
<app-vault-timeout-input
|
||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||
[formControl]="form.controls.vaultTimeout"
|
||||
ngDefaultControl
|
||||
>
|
||||
</app-vault-timeout-input>
|
||||
<div class="box-content-row display-block" appBoxRow>
|
||||
<label for="vaultTimeoutAction">{{ "vaultTimeoutAction" | i18n }}</label>
|
||||
<select
|
||||
id="vaultTimeoutAction"
|
||||
name="VaultTimeoutActions"
|
||||
formControlName="vaultTimeoutAction"
|
||||
>
|
||||
<option *ngFor="let action of availableVaultTimeoutActions" [ngValue]="action">
|
||||
{{ action | i18n }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="!availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)"
|
||||
id="unlockMethodHelp"
|
||||
class="box-footer"
|
||||
>
|
||||
{{ "unlockMethodNeededToChangeTimeoutActionDesc" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "otherOptions" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="fingerprint()"
|
||||
>
|
||||
<div class="row-main">{{ "fingerprintPhrase" | i18n }}</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="twoStep()"
|
||||
>
|
||||
<div class="row-main">{{ "twoStepLogin" | i18n }}</div>
|
||||
<i class="bwi bwi-external-link bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="changePassword()"
|
||||
*ngIf="showChangeMasterPass"
|
||||
>
|
||||
<div class="row-main">{{ "changeMasterPassword" | i18n }}</div>
|
||||
<i class="bwi bwi-external-link bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="
|
||||
!accountSwitcherEnabled && availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)
|
||||
"
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="lock()"
|
||||
>
|
||||
<div class="row-main">{{ "lockNow" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!accountSwitcherEnabled"
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="logOut()"
|
||||
>
|
||||
<div class="row-main">{{ "logOut" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
|
@ -1,6 +1,5 @@
|
|||
import { ChangeDetectorRef, Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
|
@ -17,13 +16,13 @@ import {
|
|||
} from "rxjs";
|
||||
|
||||
import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
|
||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { DeviceType } from "@bitwarden/common/enums";
|
||||
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
|
@ -34,35 +33,20 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
|||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { SetPinComponent } from "../../auth/popup/components/set-pin.component";
|
||||
import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErrors";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import { enableAccountSwitching } from "../../platform/flags";
|
||||
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
|
||||
import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors";
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import { enableAccountSwitching } from "../../../platform/flags";
|
||||
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
|
||||
import { SetPinComponent } from "../components/set-pin.component";
|
||||
|
||||
import { AboutComponent } from "./about.component";
|
||||
import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component";
|
||||
|
||||
const RateUrls = {
|
||||
[DeviceType.ChromeExtension]:
|
||||
"https://chromewebstore.google.com/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews",
|
||||
[DeviceType.FirefoxExtension]:
|
||||
"https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/#reviews",
|
||||
[DeviceType.OperaExtension]:
|
||||
"https://addons.opera.com/en/extensions/details/bitwarden-free-password-manager/#feedback-container",
|
||||
[DeviceType.EdgeExtension]:
|
||||
"https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh",
|
||||
[DeviceType.VivaldiExtension]:
|
||||
"https://chromewebstore.google.com/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews",
|
||||
[DeviceType.SafariExtension]: "https://apps.apple.com/app/bitwarden/id1352778147",
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: "app-settings",
|
||||
templateUrl: "settings.component.html",
|
||||
selector: "auth-account-security",
|
||||
templateUrl: "account-security.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class SettingsComponent implements OnInit {
|
||||
export class AccountSecurityComponent implements OnInit {
|
||||
protected readonly VaultTimeoutAction = VaultTimeoutAction;
|
||||
|
||||
availableVaultTimeoutActions: VaultTimeoutAction[] = [];
|
||||
|
@ -88,6 +72,7 @@ export class SettingsComponent implements OnInit {
|
|||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private pinService: PinServiceAbstraction,
|
||||
private policyService: PolicyService,
|
||||
private formBuilder: FormBuilder,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
|
@ -95,7 +80,6 @@ export class SettingsComponent implements OnInit {
|
|||
private vaultTimeoutService: VaultTimeoutService,
|
||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
public messagingService: MessagingService,
|
||||
private router: Router,
|
||||
private environmentService: EnvironmentService,
|
||||
private cryptoService: CryptoService,
|
||||
private stateService: StateService,
|
||||
|
@ -149,7 +133,6 @@ export class SettingsComponent implements OnInit {
|
|||
if (timeout === -2 && !showOnLocked) {
|
||||
timeout = -1;
|
||||
}
|
||||
const pinStatus = await this.vaultTimeoutSettingsService.isPinLockSet();
|
||||
|
||||
this.form.controls.vaultTimeout.valueChanges
|
||||
.pipe(
|
||||
|
@ -171,12 +154,14 @@ export class SettingsComponent implements OnInit {
|
|||
)
|
||||
.subscribe();
|
||||
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
|
||||
const initialValues = {
|
||||
vaultTimeout: timeout,
|
||||
vaultTimeoutAction: await firstValueFrom(
|
||||
this.vaultTimeoutSettingsService.vaultTimeoutAction$(),
|
||||
),
|
||||
pin: pinStatus !== "DISABLED",
|
||||
pin: await this.pinService.isPinSet(userId),
|
||||
biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(),
|
||||
enableAutoBiometricsPrompt: await firstValueFrom(
|
||||
this.biometricStateService.promptAutomatically$,
|
||||
|
@ -425,23 +410,6 @@ export class SettingsComponent implements OnInit {
|
|||
);
|
||||
}
|
||||
|
||||
async lock() {
|
||||
await this.vaultTimeoutService.lock();
|
||||
}
|
||||
|
||||
async logOut() {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "logOut" },
|
||||
content: { key: "logOutConfirmation" },
|
||||
type: "info",
|
||||
});
|
||||
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
if (confirmed) {
|
||||
this.messagingService.send("logout", { userId: userId });
|
||||
}
|
||||
}
|
||||
|
||||
async changePassword() {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "continueToWebApp" },
|
||||
|
@ -468,44 +436,6 @@ export class SettingsComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
async share() {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "learnOrg" },
|
||||
content: { key: "learnOrgConfirmation" },
|
||||
type: "info",
|
||||
});
|
||||
if (confirmed) {
|
||||
// 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
|
||||
BrowserApi.createNewTab("https://bitwarden.com/help/about-organizations/");
|
||||
}
|
||||
}
|
||||
|
||||
async webVault() {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const url = env.getWebVaultUrl();
|
||||
await BrowserApi.createNewTab(url);
|
||||
}
|
||||
|
||||
async import() {
|
||||
await this.router.navigate(["/import"]);
|
||||
if (await BrowserApi.isPopupOpen()) {
|
||||
// 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
|
||||
BrowserPopupUtils.openCurrentPagePopout(window);
|
||||
}
|
||||
}
|
||||
|
||||
export() {
|
||||
// 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.router.navigate(["/export"]);
|
||||
}
|
||||
|
||||
about() {
|
||||
this.dialogService.open(AboutComponent);
|
||||
}
|
||||
|
||||
async fingerprint() {
|
||||
const fingerprint = await this.cryptoService.getFingerprint(
|
||||
await this.stateService.getUserId(),
|
||||
|
@ -518,11 +448,21 @@ export class SettingsComponent implements OnInit {
|
|||
return firstValueFrom(dialogRef.closed);
|
||||
}
|
||||
|
||||
rate() {
|
||||
const deviceType = this.platformUtilsService.getDevice();
|
||||
// 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
|
||||
BrowserApi.createNewTab((RateUrls as any)[deviceType]);
|
||||
async lock() {
|
||||
await this.vaultTimeoutService.lock();
|
||||
}
|
||||
|
||||
async logOut() {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "logOut" },
|
||||
content: { key: "logOutConfirmation" },
|
||||
type: "info",
|
||||
});
|
||||
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
if (confirmed) {
|
||||
this.messagingService.send("logout", { userId: userId });
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
|
@ -1,7 +1,7 @@
|
|||
<form #form (ngSubmit)="submit()">
|
||||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<button type="button" routerLink="/notifications">
|
||||
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
|
@ -8,8 +8,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
|||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import { enableAccountSwitching } from "../../platform/flags";
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import { enableAccountSwitching } from "../../../platform/flags";
|
||||
|
||||
interface ExcludedDomain {
|
||||
uri: string;
|
|
@ -0,0 +1,89 @@
|
|||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "notifications" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right">
|
||||
<app-pop-out></app-pop-out>
|
||||
</div>
|
||||
</header>
|
||||
<main tabindex="-1">
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="use-passkeys">{{ "enableUsePasskeys" | i18n }}</label>
|
||||
<input
|
||||
id="use-passkeys"
|
||||
type="checkbox"
|
||||
aria-describedby="use-passkeysHelp"
|
||||
(change)="updateEnablePasskeys()"
|
||||
[(ngModel)]="enablePasskeys"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="use-passkeysHelp" class="box-footer">
|
||||
{{ "usePasskeysDesc" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="addlogin-notification-bar">{{ "enableAddLoginNotification" | i18n }}</label>
|
||||
<input
|
||||
id="addlogin-notification-bar"
|
||||
type="checkbox"
|
||||
aria-describedby="addlogin-notification-barHelp"
|
||||
(change)="updateAddLoginNotification()"
|
||||
[(ngModel)]="enableAddLoginNotification"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="addlogin-notification-barHelp" class="box-footer">
|
||||
{{
|
||||
accountSwitcherEnabled
|
||||
? ("addLoginNotificationDescAlt" | i18n)
|
||||
: ("addLoginNotificationDesc" | i18n)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="changedpass-notification-bar">{{
|
||||
"enableChangedPasswordNotification" | i18n
|
||||
}}</label>
|
||||
<input
|
||||
id="changedpass-notification-bar"
|
||||
type="checkbox"
|
||||
aria-describedby="changedpass-notification-barHelp"
|
||||
(change)="updateChangedPasswordNotification()"
|
||||
[(ngModel)]="enableChangedPasswordNotification"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="changedpass-notification-barHelp" class="box-footer">
|
||||
{{
|
||||
accountSwitcherEnabled
|
||||
? ("changedPasswordNotificationDescAlt" | i18n)
|
||||
: ("changedPasswordNotificationDesc" | i18n)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/excluded-domains"
|
||||
>
|
||||
<div class="row-main">{{ "excludedDomains" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
|
@ -0,0 +1,53 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
|
||||
import { enableAccountSwitching } from "../../../platform/flags";
|
||||
|
||||
@Component({
|
||||
selector: "autofill-notification-settings",
|
||||
templateUrl: "notifications.component.html",
|
||||
})
|
||||
export class NotifcationsSettingsComponent implements OnInit {
|
||||
enableAddLoginNotification = false;
|
||||
enableChangedPasswordNotification = false;
|
||||
enablePasskeys = true;
|
||||
accountSwitcherEnabled = false;
|
||||
|
||||
constructor(
|
||||
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
|
||||
private vaultSettingsService: VaultSettingsService,
|
||||
) {
|
||||
this.accountSwitcherEnabled = enableAccountSwitching();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.enableAddLoginNotification = await firstValueFrom(
|
||||
this.userNotificationSettingsService.enableAddedLoginPrompt$,
|
||||
);
|
||||
|
||||
this.enableChangedPasswordNotification = await firstValueFrom(
|
||||
this.userNotificationSettingsService.enableChangedPasswordPrompt$,
|
||||
);
|
||||
|
||||
this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$);
|
||||
}
|
||||
|
||||
async updateAddLoginNotification() {
|
||||
await this.userNotificationSettingsService.setEnableAddedLoginPrompt(
|
||||
this.enableAddLoginNotification,
|
||||
);
|
||||
}
|
||||
|
||||
async updateChangedPasswordNotification() {
|
||||
await this.userNotificationSettingsService.setEnableChangedPasswordPrompt(
|
||||
this.enableChangedPasswordNotification,
|
||||
);
|
||||
}
|
||||
|
||||
async updateEnablePasskeys() {
|
||||
await this.vaultSettingsService.setEnablePasskeys(this.enablePasskeys);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import { Subject, firstValueFrom, map, merge, timeout } from "rxjs";
|
||||
import { Subject, filter, firstValueFrom, map, merge, timeout } from "rxjs";
|
||||
|
||||
import {
|
||||
PinCryptoServiceAbstraction,
|
||||
PinCryptoService,
|
||||
PinServiceAbstraction,
|
||||
PinService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
UserDecryptionOptionsService,
|
||||
AuthRequestServiceAbstraction,
|
||||
|
@ -84,7 +84,6 @@ import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwar
|
|||
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
|
@ -246,10 +245,9 @@ export default class MainBackground {
|
|||
messagingService: MessageSender;
|
||||
storageService: BrowserLocalStorageService;
|
||||
secureStorageService: AbstractStorageService;
|
||||
memoryStorageService: AbstractMemoryStorageService;
|
||||
memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService;
|
||||
largeObjectMemoryStorageForStateProviders: AbstractMemoryStorageService &
|
||||
ObservableStorageService;
|
||||
memoryStorageService: AbstractStorageService;
|
||||
memoryStorageForStateProviders: AbstractStorageService & ObservableStorageService;
|
||||
largeObjectMemoryStorageForStateProviders: AbstractStorageService & ObservableStorageService;
|
||||
i18nService: I18nServiceAbstraction;
|
||||
platformUtilsService: PlatformUtilsServiceAbstraction;
|
||||
logService: LogServiceAbstraction;
|
||||
|
@ -320,7 +318,7 @@ export default class MainBackground {
|
|||
authRequestService: AuthRequestServiceAbstraction;
|
||||
accountService: AccountServiceAbstraction;
|
||||
globalStateProvider: GlobalStateProvider;
|
||||
pinCryptoService: PinCryptoServiceAbstraction;
|
||||
pinService: PinServiceAbstraction;
|
||||
singleUserStateProvider: SingleUserStateProvider;
|
||||
activeUserStateProvider: ActiveUserStateProvider;
|
||||
derivedStateProvider: DerivedStateProvider;
|
||||
|
@ -544,13 +542,31 @@ export default class MainBackground {
|
|||
|
||||
const themeStateService = new DefaultThemeStateService(this.globalStateProvider);
|
||||
|
||||
this.masterPasswordService = new MasterPasswordService(this.stateProvider);
|
||||
this.masterPasswordService = new MasterPasswordService(
|
||||
this.stateProvider,
|
||||
this.stateService,
|
||||
this.keyGenerationService,
|
||||
this.encryptService,
|
||||
);
|
||||
|
||||
this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider);
|
||||
|
||||
this.kdfConfigService = new KdfConfigService(this.stateProvider);
|
||||
|
||||
this.pinService = new PinService(
|
||||
this.accountService,
|
||||
this.cryptoFunctionService,
|
||||
this.encryptService,
|
||||
this.kdfConfigService,
|
||||
this.keyGenerationService,
|
||||
this.logService,
|
||||
this.masterPasswordService,
|
||||
this.stateProvider,
|
||||
this.stateService,
|
||||
);
|
||||
|
||||
this.cryptoService = new BrowserCryptoService(
|
||||
this.pinService,
|
||||
this.masterPasswordService,
|
||||
this.keyGenerationService,
|
||||
this.cryptoFunctionService,
|
||||
|
@ -631,6 +647,7 @@ export default class MainBackground {
|
|||
this.stateProvider,
|
||||
this.secureStorageService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
this.devicesService = new DevicesServiceImplementation(this.devicesApiService);
|
||||
|
@ -694,6 +711,8 @@ export default class MainBackground {
|
|||
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
||||
|
||||
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
|
||||
this.accountService,
|
||||
this.pinService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.cryptoService,
|
||||
this.tokenService,
|
||||
|
@ -702,14 +721,6 @@ export default class MainBackground {
|
|||
this.biometricStateService,
|
||||
);
|
||||
|
||||
this.pinCryptoService = new PinCryptoService(
|
||||
this.stateService,
|
||||
this.cryptoService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.logService,
|
||||
this.kdfConfigService,
|
||||
);
|
||||
|
||||
this.userVerificationService = new UserVerificationService(
|
||||
this.stateService,
|
||||
this.cryptoService,
|
||||
|
@ -718,7 +729,7 @@ export default class MainBackground {
|
|||
this.i18nService,
|
||||
this.userVerificationApiService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.pinCryptoService,
|
||||
this.pinService,
|
||||
this.logService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.platformUtilsService,
|
||||
|
@ -840,11 +851,13 @@ export default class MainBackground {
|
|||
this.i18nService,
|
||||
this.collectionService,
|
||||
this.cryptoService,
|
||||
this.pinService,
|
||||
);
|
||||
|
||||
this.individualVaultExportService = new IndividualVaultExportService(
|
||||
this.folderService,
|
||||
this.cipherService,
|
||||
this.pinService,
|
||||
this.cryptoService,
|
||||
this.cryptoFunctionService,
|
||||
this.kdfConfigService,
|
||||
|
@ -853,6 +866,7 @@ export default class MainBackground {
|
|||
this.organizationVaultExportService = new OrganizationVaultExportService(
|
||||
this.cipherService,
|
||||
this.apiService,
|
||||
this.pinService,
|
||||
this.cryptoService,
|
||||
this.cryptoFunctionService,
|
||||
this.collectionService,
|
||||
|
@ -903,6 +917,7 @@ export default class MainBackground {
|
|||
};
|
||||
|
||||
this.systemService = new SystemService(
|
||||
this.pinService,
|
||||
this.messagingService,
|
||||
this.platformUtilsService,
|
||||
systemUtilsServiceReloadCallback,
|
||||
|
@ -1200,31 +1215,46 @@ export default class MainBackground {
|
|||
}
|
||||
|
||||
async logout(expired: boolean, userId?: UserId) {
|
||||
userId ??= (
|
||||
await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
timeout({
|
||||
first: 2000,
|
||||
with: () => {
|
||||
throw new Error("No active account found to logout");
|
||||
},
|
||||
}),
|
||||
),
|
||||
)
|
||||
)?.id;
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
map((a) => a?.id),
|
||||
timeout({
|
||||
first: 2000,
|
||||
with: () => {
|
||||
throw new Error("No active account found to logout");
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await this.eventUploadService.uploadEvents(userId as UserId);
|
||||
const userBeingLoggedOut = userId ?? activeUserId;
|
||||
|
||||
await this.eventUploadService.uploadEvents(userBeingLoggedOut);
|
||||
|
||||
// HACK: We shouldn't wait for the authentication status to change but instead subscribe to the
|
||||
// authentication status to do various actions.
|
||||
const logoutPromise = firstValueFrom(
|
||||
this.authService.authStatusFor$(userBeingLoggedOut).pipe(
|
||||
filter((authenticationStatus) => authenticationStatus === AuthenticationStatus.LoggedOut),
|
||||
timeout({
|
||||
first: 5_000,
|
||||
with: () => {
|
||||
throw new Error("The logout process did not complete in a reasonable amount of time.");
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
this.syncService.setLastSync(new Date(0), userId),
|
||||
this.cryptoService.clearKeys(userId),
|
||||
this.cipherService.clear(userId),
|
||||
this.folderService.clear(userId),
|
||||
this.collectionService.clear(userId),
|
||||
this.passwordGenerationService.clear(userId),
|
||||
this.vaultTimeoutSettingsService.clear(userId),
|
||||
this.syncService.setLastSync(new Date(0), userBeingLoggedOut),
|
||||
this.cryptoService.clearKeys(userBeingLoggedOut),
|
||||
this.cipherService.clear(userBeingLoggedOut),
|
||||
this.folderService.clear(userBeingLoggedOut),
|
||||
this.collectionService.clear(userBeingLoggedOut),
|
||||
this.passwordGenerationService.clear(userBeingLoggedOut),
|
||||
this.vaultTimeoutSettingsService.clear(userBeingLoggedOut),
|
||||
this.vaultFilterService.clear(),
|
||||
this.biometricStateService.logout(userId),
|
||||
this.biometricStateService.logout(userBeingLoggedOut),
|
||||
/* We intentionally do not clear:
|
||||
* - autofillSettingsService
|
||||
* - badgeSettingsService
|
||||
|
@ -1235,20 +1265,28 @@ export default class MainBackground {
|
|||
//Needs to be checked before state is cleaned
|
||||
const needStorageReseed = await this.needsStorageReseed();
|
||||
|
||||
const newActiveUser = await firstValueFrom(
|
||||
this.accountService.nextUpAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
await this.stateService.clean({ userId: userId });
|
||||
await this.accountService.clean(userId);
|
||||
const newActiveUser =
|
||||
userBeingLoggedOut === activeUserId
|
||||
? await firstValueFrom(this.accountService.nextUpAccount$.pipe(map((a) => a?.id)))
|
||||
: null;
|
||||
|
||||
await this.stateEventRunnerService.handleEvent("logout", userId);
|
||||
await this.stateService.clean({ userId: userBeingLoggedOut });
|
||||
await this.accountService.clean(userBeingLoggedOut);
|
||||
|
||||
await this.stateEventRunnerService.handleEvent("logout", userBeingLoggedOut);
|
||||
|
||||
// HACK: Wait for the user logging outs authentication status to transition to LoggedOut
|
||||
await logoutPromise;
|
||||
|
||||
await this.switchAccount(newActiveUser);
|
||||
if (newActiveUser != null) {
|
||||
// we have a new active user, do not continue tearing down application
|
||||
await this.switchAccount(newActiveUser as UserId);
|
||||
this.messagingService.send("switchAccountFinish");
|
||||
} else {
|
||||
this.messagingService.send("doneLoggingOut", { expired: expired, userId: userId });
|
||||
this.messagingService.send("doneLoggingOut", {
|
||||
expired: expired,
|
||||
userId: userBeingLoggedOut,
|
||||
});
|
||||
}
|
||||
|
||||
if (needStorageReseed) {
|
||||
|
|
|
@ -356,7 +356,7 @@ export class NativeMessagingBackground {
|
|||
const masterKey = new SymmetricCryptoKey(
|
||||
Utils.fromB64ToArray(message.keyB64),
|
||||
) as MasterKey;
|
||||
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(
|
||||
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
|
||||
masterKey,
|
||||
encUserKey,
|
||||
);
|
||||
|
|
|
@ -8,6 +8,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
|||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||
import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
|
||||
|
@ -324,9 +325,10 @@ export default class RuntimeBackground {
|
|||
|
||||
if (this.onInstalledReason != null) {
|
||||
if (this.onInstalledReason === "install") {
|
||||
// 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
|
||||
BrowserApi.createNewTab("https://bitwarden.com/browser-start/");
|
||||
if (!devFlagEnabled("skipWelcomeOnInstall")) {
|
||||
void BrowserApi.createNewTab("https://bitwarden.com/browser-start/");
|
||||
}
|
||||
|
||||
await this.autofillSettingsService.setInlineMenuVisibility(
|
||||
AutofillOverlayVisibility.OnFieldFocus,
|
||||
);
|
||||
|
|
|
@ -5,6 +5,14 @@ import {
|
|||
policyServiceFactory,
|
||||
PolicyServiceInitOptions,
|
||||
} from "../../admin-console/background/service-factories/policy-service.factory";
|
||||
import {
|
||||
accountServiceFactory,
|
||||
AccountServiceInitOptions,
|
||||
} from "../../auth/background/service-factories/account-service.factory";
|
||||
import {
|
||||
pinServiceFactory,
|
||||
PinServiceInitOptions,
|
||||
} from "../../auth/background/service-factories/pin-service.factory";
|
||||
import {
|
||||
tokenServiceFactory,
|
||||
TokenServiceInitOptions,
|
||||
|
@ -34,6 +42,8 @@ import {
|
|||
type VaultTimeoutSettingsServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type VaultTimeoutSettingsServiceInitOptions = VaultTimeoutSettingsServiceFactoryOptions &
|
||||
AccountServiceInitOptions &
|
||||
PinServiceInitOptions &
|
||||
UserDecryptionOptionsServiceInitOptions &
|
||||
CryptoServiceInitOptions &
|
||||
TokenServiceInitOptions &
|
||||
|
@ -51,6 +61,8 @@ export function vaultTimeoutSettingsServiceFactory(
|
|||
opts,
|
||||
async () =>
|
||||
new VaultTimeoutSettingsService(
|
||||
await accountServiceFactory(cache, opts),
|
||||
await pinServiceFactory(cache, opts),
|
||||
await userDecryptionOptionsServiceFactory(cache, opts),
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await tokenServiceFactory(cache, opts),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"manifest_version": 2,
|
||||
"name": "__MSG_extName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "2024.4.2",
|
||||
"version": "2024.5.0",
|
||||
"description": "__MSG_extDesc__",
|
||||
"default_locale": "en",
|
||||
"author": "Bitwarden Inc.",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"minimum_chrome_version": "102.0",
|
||||
"name": "__MSG_extName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "2024.4.2",
|
||||
"version": "2024.5.0",
|
||||
"description": "__MSG_extDesc__",
|
||||
"default_locale": "en",
|
||||
"author": "Bitwarden Inc.",
|
||||
|
|
|
@ -12,6 +12,10 @@ import {
|
|||
internalMasterPasswordServiceFactory,
|
||||
MasterPasswordServiceInitOptions,
|
||||
} from "../../../auth/background/service-factories/master-password-service.factory";
|
||||
import {
|
||||
PinServiceInitOptions,
|
||||
pinServiceFactory,
|
||||
} from "../../../auth/background/service-factories/pin-service.factory";
|
||||
import {
|
||||
StateServiceInitOptions,
|
||||
stateServiceFactory,
|
||||
|
@ -45,6 +49,7 @@ import { StateProviderInitOptions, stateProviderFactory } from "./state-provider
|
|||
type CryptoServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type CryptoServiceInitOptions = CryptoServiceFactoryOptions &
|
||||
PinServiceInitOptions &
|
||||
MasterPasswordServiceInitOptions &
|
||||
KeyGenerationServiceInitOptions &
|
||||
CryptoFunctionServiceInitOptions &
|
||||
|
@ -67,6 +72,7 @@ export function cryptoServiceFactory(
|
|||
opts,
|
||||
async () =>
|
||||
new BrowserCryptoService(
|
||||
await pinServiceFactory(cache, opts),
|
||||
await internalMasterPasswordServiceFactory(cache, opts),
|
||||
await keyGenerationServiceFactory(cache, opts),
|
||||
await cryptoFunctionServiceFactory(cache, opts),
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
|
@ -66,9 +65,9 @@ export function sessionStorageServiceFactory(
|
|||
}
|
||||
|
||||
export function memoryStorageServiceFactory(
|
||||
cache: { memoryStorageService?: AbstractMemoryStorageService } & CachedServices,
|
||||
cache: { memoryStorageService?: AbstractStorageService } & CachedServices,
|
||||
opts: MemoryStorageServiceInitOptions,
|
||||
): Promise<AbstractMemoryStorageService> {
|
||||
): Promise<AbstractStorageService> {
|
||||
return factory(cache, "memoryStorageService", opts, async () => {
|
||||
if (BrowserApi.isManifestVersion(3)) {
|
||||
return new LocalBackedSessionStorageService(
|
||||
|
@ -97,10 +96,10 @@ export function memoryStorageServiceFactory(
|
|||
|
||||
export function observableMemoryStorageServiceFactory(
|
||||
cache: {
|
||||
memoryStorageService?: AbstractMemoryStorageService & ObservableStorageService;
|
||||
memoryStorageService?: AbstractStorageService & ObservableStorageService;
|
||||
} & CachedServices,
|
||||
opts: MemoryStorageServiceInitOptions,
|
||||
): Promise<AbstractMemoryStorageService & ObservableStorageService> {
|
||||
): Promise<AbstractStorageService & ObservableStorageService> {
|
||||
return factory(cache, "memoryStorageService", opts, async () => {
|
||||
return new BackgroundMemoryStorageService();
|
||||
});
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||
|
||||
import { DefaultBrowserStateService } from "../../services/default-browser-state.service";
|
||||
|
||||
import { browserSession } from "./browser-session.decorator";
|
||||
import { SessionStorable } from "./session-storable";
|
||||
import { sessionSync } from "./session-sync.decorator";
|
||||
|
||||
// browserSession initializes SessionSyncers for each sessionSync decorated property
|
||||
// We don't want to test SessionSyncers, so we'll mock them
|
||||
jest.mock("./session-syncer");
|
||||
|
||||
describe("browserSession decorator", () => {
|
||||
it("should throw if neither StateService nor MemoryStorageService is a constructor argument", () => {
|
||||
@browserSession
|
||||
class TestClass {}
|
||||
expect(() => {
|
||||
new TestClass();
|
||||
}).toThrowError(
|
||||
"Cannot decorate TestClass with browserSession, Browser's AbstractMemoryStorageService must be accessible through the observed classes parameters",
|
||||
);
|
||||
});
|
||||
|
||||
it("should create if StateService is a constructor argument", () => {
|
||||
const stateService = Object.create(DefaultBrowserStateService.prototype, {
|
||||
memoryStorageService: {
|
||||
value: Object.create(MemoryStorageService.prototype, {
|
||||
type: { value: MemoryStorageService.TYPE },
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@browserSession
|
||||
class TestClass {
|
||||
constructor(private stateService: DefaultBrowserStateService) {}
|
||||
}
|
||||
|
||||
expect(new TestClass(stateService)).toBeDefined();
|
||||
});
|
||||
|
||||
it("should create if MemoryStorageService is a constructor argument", () => {
|
||||
const memoryStorageService = Object.create(MemoryStorageService.prototype, {
|
||||
type: { value: MemoryStorageService.TYPE },
|
||||
});
|
||||
|
||||
@browserSession
|
||||
class TestClass {
|
||||
constructor(private memoryStorageService: AbstractMemoryStorageService) {}
|
||||
}
|
||||
|
||||
expect(new TestClass(memoryStorageService)).toBeDefined();
|
||||
});
|
||||
|
||||
describe("interaction with @sessionSync decorator", () => {
|
||||
let memoryStorageService: MemoryStorageService;
|
||||
|
||||
@browserSession
|
||||
class TestClass {
|
||||
@sessionSync({ initializer: (s: string) => s })
|
||||
private behaviorSubject = new BehaviorSubject("");
|
||||
|
||||
constructor(private memoryStorageService: MemoryStorageService) {}
|
||||
|
||||
fromJSON(json: any) {
|
||||
this.behaviorSubject.next(json);
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
memoryStorageService = Object.create(MemoryStorageService.prototype, {
|
||||
type: { value: MemoryStorageService.TYPE },
|
||||
});
|
||||
});
|
||||
|
||||
it("should create a session syncer", () => {
|
||||
const testClass = new TestClass(memoryStorageService) as any as SessionStorable;
|
||||
expect(testClass.__sessionSyncers.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should initialize the session syncer", () => {
|
||||
const testClass = new TestClass(memoryStorageService) as any as SessionStorable;
|
||||
expect(testClass.__sessionSyncers[0].init).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,75 +0,0 @@
|
|||
import { Constructor } from "type-fest";
|
||||
|
||||
import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
|
||||
import { SessionStorable } from "./session-storable";
|
||||
import { SessionSyncer } from "./session-syncer";
|
||||
import { SyncedItemMetadata } from "./sync-item-metadata";
|
||||
|
||||
/**
|
||||
* Mark the class as syncing state across the browser session. This decorator finds rxjs BehaviorSubject properties
|
||||
* marked with @sessionSync and syncs these values across the browser session.
|
||||
*
|
||||
* @param constructor
|
||||
* @returns A new constructor that extends the original one to add session syncing.
|
||||
*/
|
||||
export function browserSession<TCtor extends Constructor<any>>(constructor: TCtor) {
|
||||
return class extends constructor implements SessionStorable {
|
||||
__syncedItemMetadata: SyncedItemMetadata[];
|
||||
__sessionSyncers: SessionSyncer[];
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
|
||||
// Require state service to be injected
|
||||
const storageService: AbstractMemoryStorageService = this.findStorageService(
|
||||
[this as any].concat(args),
|
||||
);
|
||||
|
||||
if (this.__syncedItemMetadata == null || !(this.__syncedItemMetadata instanceof Array)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.__sessionSyncers = this.__syncedItemMetadata.map((metadata) =>
|
||||
this.buildSyncer(metadata, storageService),
|
||||
);
|
||||
}
|
||||
|
||||
buildSyncer(metadata: SyncedItemMetadata, storageSerice: AbstractMemoryStorageService) {
|
||||
const syncer = new SessionSyncer(
|
||||
(this as any)[metadata.propertyKey],
|
||||
storageSerice,
|
||||
metadata,
|
||||
);
|
||||
// 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
|
||||
syncer.init();
|
||||
return syncer;
|
||||
}
|
||||
|
||||
findStorageService(args: any[]): AbstractMemoryStorageService {
|
||||
const storageService = args.find(this.isMemoryStorageService);
|
||||
|
||||
if (storageService) {
|
||||
return storageService;
|
||||
}
|
||||
|
||||
const stateService = args.find(
|
||||
(arg) =>
|
||||
arg?.memoryStorageService != null &&
|
||||
this.isMemoryStorageService(arg.memoryStorageService),
|
||||
);
|
||||
if (stateService) {
|
||||
return stateService.memoryStorageService;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Cannot decorate ${constructor.name} with browserSession, Browser's AbstractMemoryStorageService must be accessible through the observed classes parameters`,
|
||||
);
|
||||
}
|
||||
|
||||
isMemoryStorageService(arg: any): arg is AbstractMemoryStorageService {
|
||||
return arg.type != null && arg.type === AbstractMemoryStorageService.TYPE;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export { browserSession } from "./browser-session.decorator";
|
||||
export { sessionSync } from "./session-sync.decorator";
|
|
@ -1,7 +0,0 @@
|
|||
import { SessionSyncer } from "./session-syncer";
|
||||
import { SyncedItemMetadata } from "./sync-item-metadata";
|
||||
|
||||
export interface SessionStorable {
|
||||
__syncedItemMetadata: SyncedItemMetadata[];
|
||||
__sessionSyncers: SessionSyncer[];
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { sessionSync } from "./session-sync.decorator";
|
||||
|
||||
describe("sessionSync decorator", () => {
|
||||
const initializer = (s: string) => "test";
|
||||
class TestClass {
|
||||
@sessionSync({ initializer: initializer })
|
||||
private testProperty = new BehaviorSubject("");
|
||||
@sessionSync({ initializer: initializer, initializeAs: "array" })
|
||||
private secondTestProperty = new BehaviorSubject("");
|
||||
|
||||
complete() {
|
||||
this.testProperty.complete();
|
||||
this.secondTestProperty.complete();
|
||||
}
|
||||
}
|
||||
|
||||
it("should add __syncedItemKeys to prototype", () => {
|
||||
const testClass = new TestClass();
|
||||
expect((testClass as any).__syncedItemMetadata).toEqual([
|
||||
expect.objectContaining({
|
||||
propertyKey: "testProperty",
|
||||
sessionKey: "testProperty_0",
|
||||
initializer: initializer,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
propertyKey: "secondTestProperty",
|
||||
sessionKey: "secondTestProperty_1",
|
||||
initializer: initializer,
|
||||
initializeAs: "array",
|
||||
}),
|
||||
]);
|
||||
testClass.complete();
|
||||
});
|
||||
|
||||
class TestClass2 {
|
||||
@sessionSync({ initializer: initializer })
|
||||
private testProperty = new BehaviorSubject("");
|
||||
|
||||
complete() {
|
||||
this.testProperty.complete();
|
||||
}
|
||||
}
|
||||
|
||||
it("should maintain sessionKey index count for other test classes", () => {
|
||||
const testClass = new TestClass2();
|
||||
expect((testClass as any).__syncedItemMetadata).toEqual([
|
||||
expect.objectContaining({
|
||||
propertyKey: "testProperty",
|
||||
sessionKey: "testProperty_2",
|
||||
initializer: initializer,
|
||||
}),
|
||||
]);
|
||||
testClass.complete();
|
||||
});
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { SessionStorable } from "./session-storable";
|
||||
import { InitializeOptions } from "./sync-item-metadata";
|
||||
|
||||
class BuildOptions<T, TJson = Jsonify<T>> {
|
||||
initializer?: (keyValuePair: TJson) => T;
|
||||
initializeAs?: InitializeOptions;
|
||||
}
|
||||
|
||||
// Used to ensure uniqueness for each synced observable
|
||||
let index = 0;
|
||||
|
||||
/**
|
||||
* A decorator used to indicate the BehaviorSubject should be synced for this browser session across all contexts.
|
||||
*
|
||||
* >**Note** This decorator does nothing if the enclosing class is not decorated with @browserSession.
|
||||
*
|
||||
* >**Note** The Behavior subject must be initialized with a default or in the constructor of the class. If it is not, an error will be thrown.
|
||||
*
|
||||
* >**!!Warning!!** If the property is overwritten at any time, the new value will not be synced across the browser session.
|
||||
*
|
||||
* @param buildOptions
|
||||
* Builders for the value, requires either a constructor (ctor) for your BehaviorSubject type or an
|
||||
* initializer function that takes a key value pair representation of the BehaviorSubject data
|
||||
* and returns your instantiated BehaviorSubject value. `initializeAs can optionally be used to indicate
|
||||
* the provided initializer function should be used to build an array of values. For example,
|
||||
* ```ts
|
||||
* \@sessionSync({ initializer: Foo.fromJSON, initializeAs: 'array' })
|
||||
* ```
|
||||
* is equivalent to
|
||||
* ```
|
||||
* \@sessionSync({ initializer: (obj: any[]) => obj.map((f) => Foo.fromJSON })
|
||||
* ```
|
||||
*
|
||||
* @returns decorator function
|
||||
*/
|
||||
export function sessionSync<T>(buildOptions: BuildOptions<T>) {
|
||||
return (prototype: unknown, propertyKey: string) => {
|
||||
// Force prototype into SessionStorable and implement it.
|
||||
const p = prototype as SessionStorable;
|
||||
|
||||
if (p.__syncedItemMetadata == null) {
|
||||
p.__syncedItemMetadata = [];
|
||||
}
|
||||
|
||||
p.__syncedItemMetadata.push({
|
||||
propertyKey,
|
||||
sessionKey: `${propertyKey}_${index++}`,
|
||||
initializer: buildOptions.initializer,
|
||||
initializeAs: buildOptions.initializeAs ?? "object",
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
import { awaitAsync } from "@bitwarden/common/../spec/utils";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject, ReplaySubject } from "rxjs";
|
||||
|
||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||
|
||||
import { BrowserApi } from "../../browser/browser-api";
|
||||
|
||||
import { SessionSyncer } from "./session-syncer";
|
||||
import { SyncedItemMetadata } from "./sync-item-metadata";
|
||||
|
||||
describe("session syncer", () => {
|
||||
const propertyKey = "behaviorSubject";
|
||||
const sessionKey = "Test__" + propertyKey;
|
||||
const metaData: SyncedItemMetadata = {
|
||||
propertyKey,
|
||||
sessionKey,
|
||||
initializer: (s: string) => s,
|
||||
initializeAs: "object",
|
||||
};
|
||||
let storageService: MockProxy<MemoryStorageService>;
|
||||
let sut: SessionSyncer;
|
||||
let behaviorSubject: BehaviorSubject<string>;
|
||||
|
||||
beforeEach(() => {
|
||||
behaviorSubject = new BehaviorSubject<string>("");
|
||||
jest.spyOn(chrome.runtime, "getManifest").mockReturnValue({
|
||||
name: "bitwarden-test",
|
||||
version: "0.0.0",
|
||||
manifest_version: 3,
|
||||
});
|
||||
|
||||
storageService = mock();
|
||||
storageService.has.mockResolvedValue(false);
|
||||
sut = new SessionSyncer(behaviorSubject, storageService, metaData);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
behaviorSubject.complete();
|
||||
});
|
||||
|
||||
describe("constructor", () => {
|
||||
it("should throw if subject is not an instance of Subject", () => {
|
||||
expect(() => {
|
||||
new SessionSyncer({} as any, storageService, null);
|
||||
}).toThrowError("subject must inherit from Subject");
|
||||
});
|
||||
|
||||
it("should create if either ctor or initializer is provided", () => {
|
||||
expect(
|
||||
new SessionSyncer(behaviorSubject, storageService, {
|
||||
propertyKey,
|
||||
sessionKey,
|
||||
initializeAs: "object",
|
||||
initializer: () => null,
|
||||
}),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
new SessionSyncer(behaviorSubject, storageService, {
|
||||
propertyKey,
|
||||
sessionKey,
|
||||
initializer: (s: any) => s,
|
||||
initializeAs: "object",
|
||||
}),
|
||||
).toBeDefined();
|
||||
});
|
||||
it("should throw if neither ctor or initializer is provided", () => {
|
||||
expect(() => {
|
||||
new SessionSyncer(behaviorSubject, storageService, {
|
||||
propertyKey,
|
||||
sessionKey,
|
||||
initializeAs: "object",
|
||||
initializer: null,
|
||||
});
|
||||
}).toThrowError("initializer must be provided");
|
||||
});
|
||||
});
|
||||
|
||||
describe("init", () => {
|
||||
it("should ignore all updates currently in a ReplaySubject's buffer", () => {
|
||||
const replaySubject = new ReplaySubject<string>(Infinity);
|
||||
replaySubject.next("1");
|
||||
replaySubject.next("2");
|
||||
replaySubject.next("3");
|
||||
sut = new SessionSyncer(replaySubject, storageService, metaData);
|
||||
// block observing the subject
|
||||
jest.spyOn(sut as any, "observe").mockImplementation();
|
||||
|
||||
// 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
|
||||
sut.init();
|
||||
|
||||
expect(sut["ignoreNUpdates"]).toBe(3);
|
||||
});
|
||||
|
||||
it("should ignore BehaviorSubject's initial value", () => {
|
||||
const behaviorSubject = new BehaviorSubject<string>("initial");
|
||||
sut = new SessionSyncer(behaviorSubject, storageService, metaData);
|
||||
// block observing the subject
|
||||
jest.spyOn(sut as any, "observe").mockImplementation();
|
||||
|
||||
// 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
|
||||
sut.init();
|
||||
|
||||
expect(sut["ignoreNUpdates"]).toBe(1);
|
||||
});
|
||||
|
||||
it("should grab an initial value from storage if it exists", async () => {
|
||||
storageService.has.mockResolvedValue(true);
|
||||
//Block a call to update
|
||||
const updateSpy = jest.spyOn(sut as any, "updateFromMemory").mockImplementation();
|
||||
|
||||
// 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
|
||||
sut.init();
|
||||
await awaitAsync();
|
||||
|
||||
expect(updateSpy).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it("should not grab an initial value from storage if it does not exist", async () => {
|
||||
storageService.has.mockResolvedValue(false);
|
||||
//Block a call to update
|
||||
const updateSpy = jest.spyOn(sut as any, "update").mockImplementation();
|
||||
|
||||
// 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
|
||||
sut.init();
|
||||
await awaitAsync();
|
||||
|
||||
expect(updateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("a value is emitted on the observable", () => {
|
||||
let sendMessageSpy: jest.SpyInstance;
|
||||
const value = "test";
|
||||
const serializedValue = JSON.stringify(value);
|
||||
|
||||
beforeEach(() => {
|
||||
sendMessageSpy = jest.spyOn(BrowserApi, "sendMessage");
|
||||
|
||||
// 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
|
||||
sut.init();
|
||||
|
||||
behaviorSubject.next(value);
|
||||
});
|
||||
|
||||
it("should update sessionSyncers in other contexts", async () => {
|
||||
// await finishing of fire-and-forget operation
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessageSpy).toHaveBeenCalledWith(`${sessionKey}_update`, {
|
||||
id: sut.id,
|
||||
serializedValue,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("A message is received", () => {
|
||||
let nextSpy: jest.SpyInstance;
|
||||
let sendMessageSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
nextSpy = jest.spyOn(behaviorSubject, "next");
|
||||
sendMessageSpy = jest.spyOn(BrowserApi, "sendMessage");
|
||||
|
||||
// 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
|
||||
sut.init();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should ignore messages with the wrong command", async () => {
|
||||
await sut.updateFromMessage({ command: "wrong_command", id: sut.id });
|
||||
|
||||
expect(storageService.getBypassCache).not.toHaveBeenCalled();
|
||||
expect(nextSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should ignore messages from itself", async () => {
|
||||
await sut.updateFromMessage({ command: `${sessionKey}_update`, id: sut.id });
|
||||
|
||||
expect(storageService.getBypassCache).not.toHaveBeenCalled();
|
||||
expect(nextSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should update from message on emit from another instance", async () => {
|
||||
const builder = jest.fn();
|
||||
jest.spyOn(SyncedItemMetadata, "builder").mockReturnValue(builder);
|
||||
const value = "test";
|
||||
const serializedValue = JSON.stringify(value);
|
||||
builder.mockReturnValue(value);
|
||||
|
||||
// Expect no circular messaging
|
||||
await awaitAsync();
|
||||
expect(sendMessageSpy).toHaveBeenCalledTimes(0);
|
||||
|
||||
await sut.updateFromMessage({
|
||||
command: `${sessionKey}_update`,
|
||||
id: "different_id",
|
||||
serializedValue,
|
||||
});
|
||||
await awaitAsync();
|
||||
|
||||
expect(storageService.getBypassCache).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(nextSpy).toHaveBeenCalledTimes(1);
|
||||
expect(nextSpy).toHaveBeenCalledWith(value);
|
||||
expect(behaviorSubject.value).toBe(value);
|
||||
|
||||
// Expect no circular messaging
|
||||
expect(sendMessageSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("memory storage", () => {
|
||||
const value = "test";
|
||||
const serializedValue = JSON.stringify(value);
|
||||
let saveSpy: jest.SpyInstance;
|
||||
const builder = jest.fn().mockReturnValue(value);
|
||||
const manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
|
||||
const isBackgroundPageSpy = jest.spyOn(BrowserApi, "isBackgroundPage");
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(SyncedItemMetadata, "builder").mockReturnValue(builder);
|
||||
saveSpy = jest.spyOn(storageService, "save");
|
||||
|
||||
// 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
|
||||
sut.init();
|
||||
await awaitAsync();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should always store on observed next for manifest version 3", async () => {
|
||||
manifestVersionSpy.mockReturnValue(3);
|
||||
isBackgroundPageSpy.mockReturnValueOnce(true).mockReturnValueOnce(false);
|
||||
behaviorSubject.next(value);
|
||||
await awaitAsync();
|
||||
behaviorSubject.next(value);
|
||||
await awaitAsync();
|
||||
|
||||
expect(saveSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("should not store on message receive for manifest version 3", async () => {
|
||||
manifestVersionSpy.mockReturnValue(3);
|
||||
isBackgroundPageSpy.mockReturnValueOnce(true).mockReturnValueOnce(false);
|
||||
await sut.updateFromMessage({
|
||||
command: `${sessionKey}_update`,
|
||||
id: "different_id",
|
||||
serializedValue,
|
||||
});
|
||||
await awaitAsync();
|
||||
|
||||
expect(saveSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("should store on message receive for manifest version 2 for background page only", async () => {
|
||||
manifestVersionSpy.mockReturnValue(2);
|
||||
isBackgroundPageSpy.mockReturnValueOnce(true).mockReturnValueOnce(false);
|
||||
await sut.updateFromMessage({
|
||||
command: `${sessionKey}_update`,
|
||||
id: "different_id",
|
||||
serializedValue,
|
||||
});
|
||||
await awaitAsync();
|
||||
await sut.updateFromMessage({
|
||||
command: `${sessionKey}_update`,
|
||||
id: "different_id",
|
||||
serializedValue,
|
||||
});
|
||||
await awaitAsync();
|
||||
|
||||
expect(saveSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should store on observed next for manifest version 2 for background page only", async () => {
|
||||
manifestVersionSpy.mockReturnValue(2);
|
||||
isBackgroundPageSpy.mockReturnValueOnce(true).mockReturnValueOnce(false);
|
||||
behaviorSubject.next(value);
|
||||
await awaitAsync();
|
||||
behaviorSubject.next(value);
|
||||
await awaitAsync();
|
||||
|
||||
expect(saveSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,125 +0,0 @@
|
|||
import { BehaviorSubject, concatMap, ReplaySubject, skip, Subject, Subscription } from "rxjs";
|
||||
|
||||
import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import { BrowserApi } from "../../browser/browser-api";
|
||||
|
||||
import { SyncedItemMetadata } from "./sync-item-metadata";
|
||||
|
||||
export class SessionSyncer {
|
||||
subscription: Subscription;
|
||||
id = Utils.newGuid();
|
||||
|
||||
// ignore initial values
|
||||
private ignoreNUpdates = 0;
|
||||
|
||||
constructor(
|
||||
private subject: Subject<any>,
|
||||
private memoryStorageService: AbstractMemoryStorageService,
|
||||
private metaData: SyncedItemMetadata,
|
||||
) {
|
||||
if (!(subject instanceof Subject)) {
|
||||
throw new Error("subject must inherit from Subject");
|
||||
}
|
||||
|
||||
if (metaData.initializer == null) {
|
||||
throw new Error("initializer must be provided");
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
switch (this.subject.constructor) {
|
||||
case ReplaySubject:
|
||||
// ignore all updates currently in the buffer
|
||||
this.ignoreNUpdates = (this.subject as any)._buffer.length;
|
||||
break;
|
||||
case BehaviorSubject:
|
||||
this.ignoreNUpdates = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
await this.observe();
|
||||
// must be synchronous
|
||||
const hasInSessionMemory = await this.memoryStorageService.has(this.metaData.sessionKey);
|
||||
if (hasInSessionMemory) {
|
||||
await this.updateFromMemory();
|
||||
}
|
||||
|
||||
this.listenForUpdates();
|
||||
}
|
||||
|
||||
private async observe() {
|
||||
const stream = this.subject.pipe(skip(this.ignoreNUpdates));
|
||||
this.ignoreNUpdates = 0;
|
||||
|
||||
// This may be a memory leak.
|
||||
// There is no good time to unsubscribe from this observable. Hopefully Manifest V3 clears memory from temporary
|
||||
// contexts. If so, this is handled by destruction of the context.
|
||||
this.subscription = stream
|
||||
.pipe(
|
||||
concatMap(async (next) => {
|
||||
if (this.ignoreNUpdates > 0) {
|
||||
this.ignoreNUpdates -= 1;
|
||||
return;
|
||||
}
|
||||
await this.updateSession(next);
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private listenForUpdates() {
|
||||
// This is an unawaited promise, but it will be executed asynchronously in the background.
|
||||
BrowserApi.messageListener(this.updateMessageCommand, (message) => {
|
||||
// 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.updateFromMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
async updateFromMessage(message: any) {
|
||||
if (message.command != this.updateMessageCommand || message.id === this.id) {
|
||||
return;
|
||||
}
|
||||
await this.update(message.serializedValue);
|
||||
}
|
||||
|
||||
async updateFromMemory() {
|
||||
const value = await this.memoryStorageService.getBypassCache(this.metaData.sessionKey);
|
||||
await this.update(value);
|
||||
}
|
||||
|
||||
async update(serializedValue: any) {
|
||||
if (!serializedValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unBuiltValue = JSON.parse(serializedValue);
|
||||
if (!BrowserApi.isManifestVersion(3) && BrowserApi.isBackgroundPage(self)) {
|
||||
await this.memoryStorageService.save(this.metaData.sessionKey, serializedValue);
|
||||
}
|
||||
const builder = SyncedItemMetadata.builder(this.metaData);
|
||||
const value = builder(unBuiltValue);
|
||||
this.ignoreNUpdates = 1;
|
||||
this.subject.next(value);
|
||||
}
|
||||
|
||||
private async updateSession(value: any) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serializedValue = JSON.stringify(value);
|
||||
if (BrowserApi.isManifestVersion(3) || BrowserApi.isBackgroundPage(self)) {
|
||||
await this.memoryStorageService.save(this.metaData.sessionKey, serializedValue);
|
||||
}
|
||||
await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id, serializedValue });
|
||||
}
|
||||
|
||||
private get updateMessageCommand() {
|
||||
return `${this.metaData.sessionKey}_update`;
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
export type InitializeOptions = "array" | "record" | "object";
|
||||
|
||||
export class SyncedItemMetadata {
|
||||
propertyKey: string;
|
||||
sessionKey: string;
|
||||
initializer: (keyValuePair: any) => any;
|
||||
initializeAs: InitializeOptions;
|
||||
|
||||
static builder(metadata: SyncedItemMetadata): (o: any) => any {
|
||||
const itemBuilder = metadata.initializer;
|
||||
if (metadata.initializeAs === "array") {
|
||||
return (keyValuePair: any) => keyValuePair.map((o: any) => itemBuilder(o));
|
||||
} else if (metadata.initializeAs === "record") {
|
||||
return (keyValuePair: any) => {
|
||||
const record: Record<any, any> = {};
|
||||
for (const key in keyValuePair) {
|
||||
record[key] = itemBuilder(keyValuePair[key]);
|
||||
}
|
||||
return record;
|
||||
};
|
||||
} else {
|
||||
return (keyValuePair: any) => itemBuilder(keyValuePair);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
import { SyncedItemMetadata } from "./sync-item-metadata";
|
||||
|
||||
describe("builder", () => {
|
||||
const propertyKey = "propertyKey";
|
||||
const key = "key";
|
||||
const initializer = (s: any) => "used initializer";
|
||||
|
||||
it("should use initializer", () => {
|
||||
const metadata: SyncedItemMetadata = {
|
||||
propertyKey,
|
||||
sessionKey: key,
|
||||
initializer,
|
||||
initializeAs: "object",
|
||||
};
|
||||
const builder = SyncedItemMetadata.builder(metadata);
|
||||
expect(builder({})).toBe("used initializer");
|
||||
});
|
||||
|
||||
it("should honor initialize as array", () => {
|
||||
const metadata: SyncedItemMetadata = {
|
||||
propertyKey,
|
||||
sessionKey: key,
|
||||
initializer: initializer,
|
||||
initializeAs: "array",
|
||||
};
|
||||
const builder = SyncedItemMetadata.builder(metadata);
|
||||
expect(builder([{}])).toBeInstanceOf(Array);
|
||||
expect(builder([{}])[0]).toBe("used initializer");
|
||||
});
|
||||
|
||||
it("should honor initialize as record", () => {
|
||||
const metadata: SyncedItemMetadata = {
|
||||
propertyKey,
|
||||
sessionKey: key,
|
||||
initializer: initializer,
|
||||
initializeAs: "record",
|
||||
};
|
||||
const builder = SyncedItemMetadata.builder(metadata);
|
||||
expect(builder({ key: "" })).toBeInstanceOf(Object);
|
||||
expect(builder({ key: "" })).toStrictEqual({ key: "used initializer" });
|
||||
});
|
||||
});
|
|
@ -315,13 +315,13 @@ export default {
|
|||
importProvidersFrom(
|
||||
RouterModule.forRoot(
|
||||
[
|
||||
{ path: "", redirectTo: "vault", pathMatch: "full" },
|
||||
{ path: "vault", component: MockVaultPageComponent },
|
||||
{ path: "generator", component: MockGeneratorPageComponent },
|
||||
{ path: "send", component: MockSendPageComponent },
|
||||
{ path: "settings", component: MockSettingsPageComponent },
|
||||
{ path: "", redirectTo: "tabs/vault", pathMatch: "full" },
|
||||
{ path: "tabs/vault", component: MockVaultPageComponent },
|
||||
{ path: "tabs/generator", component: MockGeneratorPageComponent },
|
||||
{ path: "tabs/send", component: MockSendPageComponent },
|
||||
{ path: "tabs/settings", component: MockSettingsPageComponent },
|
||||
// in case you are coming from a story that also uses the router
|
||||
{ path: "**", redirectTo: "vault" },
|
||||
{ path: "**", redirectTo: "tabs/vault" },
|
||||
],
|
||||
{ useHash: true },
|
||||
),
|
||||
|
|
|
@ -17,25 +17,25 @@ export class PopupTabNavigationComponent {
|
|||
navButtons = [
|
||||
{
|
||||
label: "Vault",
|
||||
page: "/vault",
|
||||
page: "/tabs/vault",
|
||||
iconKey: "lock",
|
||||
iconKeyActive: "lock-f",
|
||||
},
|
||||
{
|
||||
label: "Generator",
|
||||
page: "/generator",
|
||||
page: "/tabs/generator",
|
||||
iconKey: "generate",
|
||||
iconKeyActive: "generate-f",
|
||||
},
|
||||
{
|
||||
label: "Send",
|
||||
page: "/send",
|
||||
page: "/tabs/send",
|
||||
iconKey: "send",
|
||||
iconKeyActive: "send-f",
|
||||
},
|
||||
{
|
||||
label: "Settings",
|
||||
page: "/settings",
|
||||
page: "/tabs/settings",
|
||||
iconKey: "cog",
|
||||
iconKeyActive: "cog-f",
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
|
@ -19,6 +20,7 @@ import { UserKey } from "@bitwarden/common/types/key";
|
|||
|
||||
export class BrowserCryptoService extends CryptoService {
|
||||
constructor(
|
||||
pinService: PinServiceAbstraction,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
keyGenerationService: KeyGenerationService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
|
@ -32,6 +34,7 @@ export class BrowserCryptoService extends CryptoService {
|
|||
kdfConfigService: KdfConfigService,
|
||||
) {
|
||||
super(
|
||||
pinService,
|
||||
masterPasswordService,
|
||||
keyGenerationService,
|
||||
cryptoFunctionService,
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
|
||||
import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service";
|
||||
|
||||
export default class BrowserMemoryStorageService
|
||||
extends AbstractChromeStorageService
|
||||
implements AbstractMemoryStorageService
|
||||
{
|
||||
export default class BrowserMemoryStorageService extends AbstractChromeStorageService {
|
||||
constructor() {
|
||||
super(chrome.storage.session);
|
||||
}
|
||||
type = "MemoryStorageService" as const;
|
||||
getBypassCache<T>(key: string): Promise<T> {
|
||||
return this.get(key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,7 @@ import { mock, MockProxy } from "jest-mock-extended";
|
|||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||
import { State } from "@bitwarden/common/platform/models/domain/state";
|
||||
|
@ -18,9 +15,6 @@ import { Account } from "../../models/account";
|
|||
|
||||
import { DefaultBrowserStateService } from "./default-browser-state.service";
|
||||
|
||||
// disable session syncing to just test class
|
||||
jest.mock("../decorators/session-sync-observable/");
|
||||
|
||||
describe("Browser State Service", () => {
|
||||
let secureStorageService: MockProxy<AbstractStorageService>;
|
||||
let diskStorageService: MockProxy<AbstractStorageService>;
|
||||
|
@ -56,7 +50,7 @@ describe("Browser State Service", () => {
|
|||
});
|
||||
|
||||
describe("state methods", () => {
|
||||
let memoryStorageService: MockProxy<AbstractMemoryStorageService>;
|
||||
let memoryStorageService: MockProxy<AbstractStorageService>;
|
||||
|
||||
beforeEach(() => {
|
||||
memoryStorageService = mock();
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import {
|
||||
AbstractStorageService,
|
||||
AbstractMemoryStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
||||
|
@ -15,27 +10,19 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r
|
|||
import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service";
|
||||
|
||||
import { Account } from "../../models/account";
|
||||
import { browserSession, sessionSync } from "../decorators/session-sync-observable";
|
||||
|
||||
import { BrowserStateService } from "./abstractions/browser-state.service";
|
||||
|
||||
@browserSession
|
||||
export class DefaultBrowserStateService
|
||||
extends BaseStateService<GlobalState, Account>
|
||||
implements BrowserStateService
|
||||
{
|
||||
@sessionSync({
|
||||
initializer: Account.fromJSON as any, // TODO: Remove this any when all any types are removed from Account
|
||||
initializeAs: "record",
|
||||
})
|
||||
protected accountsSubject: BehaviorSubject<{ [userId: string]: Account }>;
|
||||
|
||||
protected accountDeserializer = Account.fromJSON;
|
||||
|
||||
constructor(
|
||||
storageService: AbstractStorageService,
|
||||
secureStorageService: AbstractStorageService,
|
||||
memoryStorageService: AbstractMemoryStorageService,
|
||||
memoryStorageService: AbstractStorageService,
|
||||
logService: LogService,
|
||||
stateFactory: StateFactory<GlobalState, Account>,
|
||||
accountService: AccountService,
|
||||
|
|
|
@ -59,24 +59,12 @@ describe("LocalBackedSessionStorage", () => {
|
|||
await sut.get("test");
|
||||
expect(sut["cache"]["test"]).toEqual("decrypted");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getBypassCache", () => {
|
||||
it("ignores cached values", async () => {
|
||||
sut["cache"]["test"] = "cached";
|
||||
const encrypted = makeEncString("encrypted");
|
||||
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||
const result = await sut.getBypassCache("test");
|
||||
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey);
|
||||
expect(result).toEqual("decrypted");
|
||||
});
|
||||
|
||||
it("returns a decrypted value when one is stored in local storage", async () => {
|
||||
const encrypted = makeEncString("encrypted");
|
||||
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||
const result = await sut.getBypassCache("test");
|
||||
const result = await sut.get("test");
|
||||
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey);
|
||||
expect(result).toEqual("decrypted");
|
||||
});
|
||||
|
@ -85,19 +73,9 @@ describe("LocalBackedSessionStorage", () => {
|
|||
const encrypted = makeEncString("encrypted");
|
||||
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||
await sut.getBypassCache("test");
|
||||
await sut.get("test");
|
||||
expect(sut["cache"]["test"]).toEqual("decrypted");
|
||||
});
|
||||
|
||||
it("deserializes when a deserializer is provided", async () => {
|
||||
const encrypted = makeEncString("encrypted");
|
||||
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||
const deserializer = jest.fn().mockReturnValue("deserialized");
|
||||
const result = await sut.getBypassCache("test", { deserializer });
|
||||
expect(deserializer).toHaveBeenCalledWith("decrypted");
|
||||
expect(result).toEqual("deserialized");
|
||||
});
|
||||
});
|
||||
|
||||
describe("has", () => {
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import { Subject } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
StorageUpdate,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { MemoryStorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
||||
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
|
@ -20,7 +18,7 @@ import { MemoryStoragePortMessage } from "../storage/port-messages";
|
|||
import { portName } from "../storage/port-name";
|
||||
|
||||
export class LocalBackedSessionStorageService
|
||||
extends AbstractMemoryStorageService
|
||||
extends AbstractStorageService
|
||||
implements ObservableStorageService
|
||||
{
|
||||
private ports: Set<chrome.runtime.Port> = new Set([]);
|
||||
|
@ -65,20 +63,12 @@ export class LocalBackedSessionStorageService
|
|||
});
|
||||
}
|
||||
|
||||
async get<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T> {
|
||||
async get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||
if (this.cache[key] !== undefined) {
|
||||
return this.cache[key] as T;
|
||||
}
|
||||
|
||||
return await this.getBypassCache(key, options);
|
||||
}
|
||||
|
||||
async getBypassCache<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T> {
|
||||
let value = await this.getLocalSessionValue(await this.sessionKey.get(), key);
|
||||
|
||||
if (options?.deserializer != null) {
|
||||
value = options.deserializer(value as Jsonify<T>);
|
||||
}
|
||||
const value = await this.getLocalSessionValue(await this.sessionKey.get(), key);
|
||||
|
||||
this.cache[key] = value;
|
||||
return value as T;
|
||||
|
@ -159,7 +149,6 @@ export class LocalBackedSessionStorageService
|
|||
|
||||
switch (message.action) {
|
||||
case "get":
|
||||
case "getBypassCache":
|
||||
case "has": {
|
||||
result = await this[message.action](message.key);
|
||||
break;
|
||||
|
|
|
@ -51,7 +51,6 @@ export class BackgroundMemoryStorageService extends MemoryStorageService {
|
|||
|
||||
switch (message.action) {
|
||||
case "get":
|
||||
case "getBypassCache":
|
||||
case "has": {
|
||||
result = await this[message.action](message.key);
|
||||
break;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Observable, Subject, filter, firstValueFrom, map } from "rxjs";
|
||||
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
StorageUpdate,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
@ -11,7 +11,7 @@ import { fromChromeEvent } from "../browser/from-chrome-event";
|
|||
import { MemoryStoragePortMessage } from "./port-messages";
|
||||
import { portName } from "./port-name";
|
||||
|
||||
export class ForegroundMemoryStorageService extends AbstractMemoryStorageService {
|
||||
export class ForegroundMemoryStorageService extends AbstractStorageService {
|
||||
private _port: chrome.runtime.Port;
|
||||
private _backgroundResponses$: Observable<MemoryStoragePortMessage>;
|
||||
private updatesSubject = new Subject<StorageUpdate>();
|
||||
|
@ -59,9 +59,6 @@ export class ForegroundMemoryStorageService extends AbstractMemoryStorageService
|
|||
async get<T>(key: string): Promise<T> {
|
||||
return await this.delegateToBackground<T>("get", key);
|
||||
}
|
||||
async getBypassCache<T>(key: string): Promise<T> {
|
||||
return await this.delegateToBackground<T>("getBypassCache", key);
|
||||
}
|
||||
async has(key: string): Promise<boolean> {
|
||||
return await this.delegateToBackground<boolean>("has", key);
|
||||
}
|
||||
|
|
|
@ -25,9 +25,9 @@ describe("foreground background memory storage interaction", () => {
|
|||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
test.each(["has", "get", "getBypassCache"])(
|
||||
test.each(["has", "get"])(
|
||||
"background should respond with the correct value for %s",
|
||||
async (action: "get" | "has" | "getBypassCache") => {
|
||||
async (action: "get" | "has") => {
|
||||
const key = "key";
|
||||
const value = "value";
|
||||
background[action] = jest.fn().mockResolvedValue(value);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
StorageUpdate,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
|
||||
|
@ -14,7 +14,7 @@ type MemoryStoragePortMessage = {
|
|||
data: string | string[] | StorageUpdate;
|
||||
originator: "foreground" | "background";
|
||||
action?:
|
||||
| keyof Pick<AbstractMemoryStorageService, "get" | "getBypassCache" | "has" | "save" | "remove">
|
||||
| keyof Pick<AbstractStorageService, "get" | "has" | "save" | "remove">
|
||||
| "subject_update"
|
||||
| "initialization";
|
||||
};
|
||||
|
|
|
@ -174,27 +174,35 @@ export const routerTransition = trigger("routerTransition", [
|
|||
transition("clone-cipher => attachments, clone-cipher => collections", inSlideLeft),
|
||||
transition("attachments => clone-cipher, collections => clone-cipher", outSlideRight),
|
||||
|
||||
transition("tabs => import", inSlideLeft),
|
||||
transition("import => tabs", outSlideRight),
|
||||
transition("tabs => account-security", inSlideLeft),
|
||||
transition("account-security => tabs", outSlideRight),
|
||||
|
||||
transition("tabs => export", inSlideLeft),
|
||||
transition("export => tabs", outSlideRight),
|
||||
// Vault settings
|
||||
transition("tabs => vault-settings", inSlideLeft),
|
||||
transition("vault-settings => tabs", outSlideRight),
|
||||
|
||||
transition("tabs => folders", inSlideLeft),
|
||||
transition("folders => tabs", outSlideRight),
|
||||
transition("vault-settings => import", inSlideLeft),
|
||||
transition("import => vault-settings", outSlideRight),
|
||||
|
||||
transition("vault-settings => export", inSlideLeft),
|
||||
transition("export => vault-settings", outSlideRight),
|
||||
|
||||
transition("vault-settings => folders", inSlideLeft),
|
||||
transition("folders => vault-settings", outSlideRight),
|
||||
|
||||
transition("folders => edit-folder, folders => add-folder", inSlideUp),
|
||||
transition("edit-folder => folders, add-folder => folders", outSlideDown),
|
||||
|
||||
transition("tabs => sync", inSlideLeft),
|
||||
transition("sync => tabs", outSlideRight),
|
||||
|
||||
transition("tabs => excluded-domains", inSlideLeft),
|
||||
transition("excluded-domains => tabs", outSlideRight),
|
||||
transition("vault-settings => sync", inSlideLeft),
|
||||
transition("sync => vault-settings", outSlideRight),
|
||||
|
||||
transition("tabs => options", inSlideLeft),
|
||||
transition("options => tabs", outSlideRight),
|
||||
|
||||
// Appearance settings
|
||||
transition("tabs => appearance", inSlideLeft),
|
||||
transition("appearance => tabs", outSlideRight),
|
||||
|
||||
transition("tabs => premium", inSlideLeft),
|
||||
transition("premium => tabs", outSlideRight),
|
||||
|
||||
|
@ -212,6 +220,13 @@ export const routerTransition = trigger("routerTransition", [
|
|||
transition("tabs => edit-send, send-type => edit-send", inSlideUp),
|
||||
transition("edit-send => tabs, edit-send => send-type", outSlideDown),
|
||||
|
||||
// Notification settings
|
||||
transition("tabs => notifications", inSlideLeft),
|
||||
transition("notifications => tabs", outSlideRight),
|
||||
|
||||
transition("notifications => excluded-domains", inSlideLeft),
|
||||
transition("excluded-domains => notifications", outSlideRight),
|
||||
|
||||
transition("tabs => autofill", inSlideLeft),
|
||||
transition("autofill => tabs", outSlideRight),
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ import { Injectable, NgModule } from "@angular/core";
|
|||
import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import {
|
||||
redirectGuard,
|
||||
AuthGuard,
|
||||
lockGuard,
|
||||
redirectGuard,
|
||||
tdeDecryptionRequiredGuard,
|
||||
unauthGuardFn,
|
||||
} from "@bitwarden/angular/auth/guards";
|
||||
|
@ -21,11 +21,14 @@ import { LoginComponent } from "../auth/popup/login.component";
|
|||
import { RegisterComponent } from "../auth/popup/register.component";
|
||||
import { RemovePasswordComponent } from "../auth/popup/remove-password.component";
|
||||
import { SetPasswordComponent } from "../auth/popup/set-password.component";
|
||||
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
|
||||
import { SsoComponent } from "../auth/popup/sso.component";
|
||||
import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component";
|
||||
import { TwoFactorComponent } from "../auth/popup/two-factor.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
|
||||
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
|
||||
import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component";
|
||||
import { NotifcationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
|
||||
import { PremiumComponent } from "../billing/popup/settings/premium.component";
|
||||
import BrowserPopupUtils from "../platform/popup/browser-popup-utils";
|
||||
import { GeneratorComponent } from "../tools/popup/generator/generator.component";
|
||||
|
@ -35,6 +38,7 @@ import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.compo
|
|||
import { SendTypeComponent } from "../tools/popup/send/send-type.component";
|
||||
import { ExportComponent } from "../tools/popup/settings/export.component";
|
||||
import { ImportBrowserComponent } from "../tools/popup/settings/import/import-browser.component";
|
||||
import { SettingsComponent } from "../tools/popup/settings/settings.component";
|
||||
import { Fido2Component } from "../vault/popup/components/fido2/fido2.component";
|
||||
import { AddEditComponent } from "../vault/popup/components/vault/add-edit.component";
|
||||
import { AttachmentsComponent } from "../vault/popup/components/vault/attachments.component";
|
||||
|
@ -44,16 +48,19 @@ import { PasswordHistoryComponent } from "../vault/popup/components/vault/passwo
|
|||
import { ShareComponent } from "../vault/popup/components/vault/share.component";
|
||||
import { VaultFilterComponent } from "../vault/popup/components/vault/vault-filter.component";
|
||||
import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items.component";
|
||||
import { VaultV2Component } from "../vault/popup/components/vault/vault-v2.component";
|
||||
import { ViewComponent } from "../vault/popup/components/vault/view.component";
|
||||
import { AppearanceComponent } from "../vault/popup/settings/appearance.component";
|
||||
import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component";
|
||||
import { FoldersComponent } from "../vault/popup/settings/folders.component";
|
||||
import { SyncComponent } from "../vault/popup/settings/sync.component";
|
||||
import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.component";
|
||||
|
||||
import { extensionRefreshRedirect, extensionRefreshSwap } from "./extension-refresh-route-utils";
|
||||
import { debounceNavigationGuard } from "./services/debounce-navigation.service";
|
||||
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
|
||||
import { FoldersComponent } from "./settings/folders.component";
|
||||
import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component";
|
||||
import { OptionsComponent } from "./settings/options.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { SyncComponent } from "./settings/sync.component";
|
||||
import { TabsV2Component } from "./tabs-v2.component";
|
||||
import { TabsComponent } from "./tabs.component";
|
||||
|
||||
const unauthRouteOverrides = {
|
||||
|
@ -244,6 +251,24 @@ const routes: Routes = [
|
|||
canActivate: [AuthGuard],
|
||||
data: { state: "autofill" },
|
||||
},
|
||||
{
|
||||
path: "account-security",
|
||||
component: AccountSecurityComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: { state: "account-security" },
|
||||
},
|
||||
{
|
||||
path: "notifications",
|
||||
component: NotifcationsSettingsComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: { state: "notifications" },
|
||||
},
|
||||
{
|
||||
path: "vault-settings",
|
||||
component: VaultSettingsComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: { state: "vault-settings" },
|
||||
},
|
||||
{
|
||||
path: "folders",
|
||||
component: FoldersComponent,
|
||||
|
@ -286,6 +311,12 @@ const routes: Routes = [
|
|||
canActivate: [AuthGuard],
|
||||
data: { state: "options" },
|
||||
},
|
||||
{
|
||||
path: "appearance",
|
||||
component: AppearanceComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: { state: "appearance" },
|
||||
},
|
||||
{
|
||||
path: "clone-cipher",
|
||||
component: AddEditComponent,
|
||||
|
@ -322,9 +353,8 @@ const routes: Routes = [
|
|||
canActivate: [AuthGuard],
|
||||
data: { state: "help-and-feedback" },
|
||||
},
|
||||
{
|
||||
...extensionRefreshSwap(TabsComponent, TabsV2Component, {
|
||||
path: "tabs",
|
||||
component: TabsComponent,
|
||||
data: { state: "tabs" },
|
||||
children: [
|
||||
{
|
||||
|
@ -336,15 +366,15 @@ const routes: Routes = [
|
|||
path: "current",
|
||||
component: CurrentTabComponent,
|
||||
canActivate: [AuthGuard],
|
||||
canMatch: [extensionRefreshRedirect("/tabs/vault")],
|
||||
data: { state: "tabs_current" },
|
||||
runGuardsAndResolvers: "always",
|
||||
},
|
||||
{
|
||||
...extensionRefreshSwap(VaultFilterComponent, VaultV2Component, {
|
||||
path: "vault",
|
||||
component: VaultFilterComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: { state: "tabs_vault" },
|
||||
},
|
||||
}),
|
||||
{
|
||||
path: "generator",
|
||||
component: GeneratorComponent,
|
||||
|
@ -364,7 +394,7 @@ const routes: Routes = [
|
|||
data: { state: "tabs_send" },
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
path: "account-switcher",
|
||||
component: AccountSwitcherComponent,
|
||||
|
|
|
@ -30,11 +30,15 @@ import { LoginComponent } from "../auth/popup/login.component";
|
|||
import { RegisterComponent } from "../auth/popup/register.component";
|
||||
import { RemovePasswordComponent } from "../auth/popup/remove-password.component";
|
||||
import { SetPasswordComponent } from "../auth/popup/set-password.component";
|
||||
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
|
||||
import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component";
|
||||
import { SsoComponent } from "../auth/popup/sso.component";
|
||||
import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component";
|
||||
import { TwoFactorComponent } from "../auth/popup/two-factor.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
|
||||
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
|
||||
import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component";
|
||||
import { NotifcationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
|
||||
import { PremiumComponent } from "../billing/popup/settings/premium.component";
|
||||
import { HeaderComponent } from "../platform/popup/header.component";
|
||||
import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.component";
|
||||
|
@ -49,6 +53,7 @@ import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.componen
|
|||
import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component";
|
||||
import { SendTypeComponent } from "../tools/popup/send/send-type.component";
|
||||
import { ExportComponent } from "../tools/popup/settings/export.component";
|
||||
import { SettingsComponent } from "../tools/popup/settings/settings.component";
|
||||
import { ActionButtonsComponent } from "../vault/popup/components/action-buttons.component";
|
||||
import { CipherRowComponent } from "../vault/popup/components/cipher-row.component";
|
||||
import { Fido2CipherRowComponent } from "../vault/popup/components/fido2/fido2-cipher-row.component";
|
||||
|
@ -64,22 +69,23 @@ import { ShareComponent } from "../vault/popup/components/vault/share.component"
|
|||
import { VaultFilterComponent } from "../vault/popup/components/vault/vault-filter.component";
|
||||
import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items.component";
|
||||
import { VaultSelectComponent } from "../vault/popup/components/vault/vault-select.component";
|
||||
import { VaultV2Component } from "../vault/popup/components/vault/vault-v2.component";
|
||||
import { ViewCustomFieldsComponent } from "../vault/popup/components/vault/view-custom-fields.component";
|
||||
import { ViewComponent } from "../vault/popup/components/vault/view.component";
|
||||
import { AppearanceComponent } from "../vault/popup/settings/appearance.component";
|
||||
import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component";
|
||||
import { FoldersComponent } from "../vault/popup/settings/folders.component";
|
||||
import { SyncComponent } from "../vault/popup/settings/sync.component";
|
||||
import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.component";
|
||||
|
||||
import { AppRoutingModule } from "./app-routing.module";
|
||||
import { AppComponent } from "./app.component";
|
||||
import { PopOutComponent } from "./components/pop-out.component";
|
||||
import { UserVerificationComponent } from "./components/user-verification.component";
|
||||
import { ServicesModule } from "./services/services.module";
|
||||
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
|
||||
import { FoldersComponent } from "./settings/folders.component";
|
||||
import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component";
|
||||
import { OptionsComponent } from "./settings/options.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { SyncComponent } from "./settings/sync.component";
|
||||
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
|
||||
import { TabsV2Component } from "./tabs-v2.component";
|
||||
import { TabsComponent } from "./tabs.component";
|
||||
|
||||
// Register the locales for the application
|
||||
|
@ -144,6 +150,8 @@ import "../platform/popup/locales";
|
|||
LoginViaAuthRequestComponent,
|
||||
LoginDecryptionOptionsComponent,
|
||||
OptionsComponent,
|
||||
NotifcationsSettingsComponent,
|
||||
AppearanceComponent,
|
||||
GeneratorComponent,
|
||||
PasswordGeneratorHistoryComponent,
|
||||
PasswordHistoryComponent,
|
||||
|
@ -155,11 +163,14 @@ import "../platform/popup/locales";
|
|||
SendListComponent,
|
||||
SendTypeComponent,
|
||||
SetPasswordComponent,
|
||||
AccountSecurityComponent,
|
||||
SettingsComponent,
|
||||
VaultSettingsComponent,
|
||||
ShareComponent,
|
||||
SsoComponent,
|
||||
SyncComponent,
|
||||
TabsComponent,
|
||||
TabsV2Component,
|
||||
TwoFactorComponent,
|
||||
TwoFactorOptionsComponent,
|
||||
UpdateTempPasswordComponent,
|
||||
|
@ -175,6 +186,7 @@ import "../platform/popup/locales";
|
|||
EnvironmentSelectorComponent,
|
||||
CurrentAccountComponent,
|
||||
AccountSwitcherComponent,
|
||||
VaultV2Component,
|
||||
],
|
||||
providers: [CurrencyPipe, DatePipe],
|
||||
bootstrap: [AppComponent],
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { inject, Type } from "@angular/core";
|
||||
import { Route, Router, Routes, UrlTree } from "@angular/router";
|
||||
|
||||
import { componentRouteSwap } from "@bitwarden/angular/utils/component-route-swap";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
/**
|
||||
* Helper function to swap between two components based on the ExtensionRefresh feature flag.
|
||||
* @param defaultComponent - The current non-refreshed component to render.
|
||||
* @param refreshedComponent - The new refreshed component to render.
|
||||
* @param options - The shared route options to apply to both components.
|
||||
*/
|
||||
export function extensionRefreshSwap(
|
||||
defaultComponent: Type<any>,
|
||||
refreshedComponent: Type<any>,
|
||||
options: Route,
|
||||
): Routes {
|
||||
return componentRouteSwap(
|
||||
defaultComponent,
|
||||
refreshedComponent,
|
||||
async () => {
|
||||
const configService = inject(ConfigService);
|
||||
return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to redirect to a new URL based on the ExtensionRefresh feature flag.
|
||||
* @param redirectUrl - The URL to redirect to if the ExtensionRefresh flag is enabled.
|
||||
*/
|
||||
export function extensionRefreshRedirect(redirectUrl: string): () => Promise<boolean | UrlTree> {
|
||||
return async () => {
|
||||
const configService = inject(ConfigService);
|
||||
const router = inject(Router);
|
||||
const shouldRedirect = await configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
|
||||
if (shouldRedirect) {
|
||||
return router.parseUrl(redirectUrl);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -424,6 +424,10 @@ img,
|
|||
.modal-title,
|
||||
.overlay-container {
|
||||
user-select: none;
|
||||
|
||||
&.user-select {
|
||||
user-select: auto;
|
||||
}
|
||||
}
|
||||
|
||||
app-about .modal-body > *,
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
CLIENT_TYPE,
|
||||
} from "@bitwarden/angular/services/injection-tokens";
|
||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { AuthRequestServiceAbstraction, PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
|
@ -59,7 +59,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
|
@ -210,6 +209,7 @@ const safeProviders: SafeProvider[] = [
|
|||
safeProvider({
|
||||
provide: CryptoService,
|
||||
useFactory: (
|
||||
pinService: PinServiceAbstraction,
|
||||
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
keyGenerationService: KeyGenerationService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
|
@ -223,6 +223,7 @@ const safeProviders: SafeProvider[] = [
|
|||
kdfConfigService: KdfConfigService,
|
||||
) => {
|
||||
const cryptoService = new BrowserCryptoService(
|
||||
pinService,
|
||||
masterPasswordService,
|
||||
keyGenerationService,
|
||||
cryptoFunctionService,
|
||||
|
@ -239,6 +240,7 @@ const safeProviders: SafeProvider[] = [
|
|||
return cryptoService;
|
||||
},
|
||||
deps: [
|
||||
PinServiceAbstraction,
|
||||
InternalMasterPasswordServiceAbstraction,
|
||||
KeyGenerationService,
|
||||
CryptoFunctionService,
|
||||
|
@ -411,7 +413,7 @@ const safeProviders: SafeProvider[] = [
|
|||
safeProvider({
|
||||
provide: OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE,
|
||||
useFactory: (
|
||||
regularMemoryStorageService: AbstractMemoryStorageService & ObservableStorageService,
|
||||
regularMemoryStorageService: AbstractStorageService & ObservableStorageService,
|
||||
) => {
|
||||
if (BrowserApi.isManifestVersion(2)) {
|
||||
return regularMemoryStorageService;
|
||||
|
@ -439,7 +441,7 @@ const safeProviders: SafeProvider[] = [
|
|||
useFactory: (
|
||||
storageService: AbstractStorageService,
|
||||
secureStorageService: AbstractStorageService,
|
||||
memoryStorageService: AbstractMemoryStorageService,
|
||||
memoryStorageService: AbstractStorageService,
|
||||
logService: LogService,
|
||||
accountService: AccountServiceAbstraction,
|
||||
environmentService: EnvironmentService,
|
||||
|
|
|
@ -192,56 +192,5 @@
|
|||
{{ "showIdentitiesCurrentTabDesc" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="favicon">{{ "enableFavicon" | i18n }}</label>
|
||||
<input
|
||||
id="favicon"
|
||||
type="checkbox"
|
||||
aria-describedby="faviconHelp"
|
||||
(change)="updateFavicon()"
|
||||
[(ngModel)]="enableFavicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="faviconHelp" class="box-footer">
|
||||
{{ accountSwitcherEnabled ? ("faviconDescAlt" | i18n) : ("faviconDesc" | i18n) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="badge">{{ "enableBadgeCounter" | i18n }}</label>
|
||||
<input
|
||||
id="badge"
|
||||
type="checkbox"
|
||||
aria-describedby="badgeHelp"
|
||||
(change)="updateBadgeCounter()"
|
||||
[(ngModel)]="enableBadgeCounter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="badgeHelp" class="box-footer">{{ "badgeCounterDesc" | i18n }}</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="theme">{{ "theme" | i18n }}</label>
|
||||
<select
|
||||
id="theme"
|
||||
name="Theme"
|
||||
aria-describedby="themeHelp"
|
||||
[(ngModel)]="theme"
|
||||
(change)="saveTheme()"
|
||||
>
|
||||
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="themeHelp" class="box-footer">
|
||||
{{ accountSwitcherEnabled ? ("themeDescAlt" | i18n) : ("themeDesc" | i18n) }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</main>
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Component, OnInit } from "@angular/core";
|
|||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
import { ClearClipboardDelaySetting } from "@bitwarden/common/autofill/types";
|
||||
|
@ -12,8 +11,6 @@ import {
|
|||
} from "@bitwarden/common/models/domain/domain-service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
|
||||
import { enableAccountSwitching } from "../../platform/flags";
|
||||
|
@ -23,8 +20,6 @@ import { enableAccountSwitching } from "../../platform/flags";
|
|||
templateUrl: "options.component.html",
|
||||
})
|
||||
export class OptionsComponent implements OnInit {
|
||||
enableFavicon = false;
|
||||
enableBadgeCounter = true;
|
||||
enableAutoFillOnPageLoad = false;
|
||||
autoFillOnPageLoadDefault = false;
|
||||
autoFillOnPageLoadOptions: any[];
|
||||
|
@ -36,8 +31,6 @@ export class OptionsComponent implements OnInit {
|
|||
showCardsCurrentTab = false;
|
||||
showIdentitiesCurrentTab = false;
|
||||
showClearClipboard = true;
|
||||
theme: ThemeType;
|
||||
themeOptions: any[];
|
||||
defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain;
|
||||
uriMatchOptions: any[];
|
||||
clearClipboard: ClearClipboardDelaySetting;
|
||||
|
@ -52,18 +45,9 @@ export class OptionsComponent implements OnInit {
|
|||
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
|
||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||
private domainSettingsService: DomainSettingsService,
|
||||
private badgeSettingsService: BadgeSettingsServiceAbstraction,
|
||||
i18nService: I18nService,
|
||||
private themeStateService: ThemeStateService,
|
||||
private vaultSettingsService: VaultSettingsService,
|
||||
) {
|
||||
this.themeOptions = [
|
||||
{ name: i18nService.t("default"), value: ThemeType.System },
|
||||
{ name: i18nService.t("light"), value: ThemeType.Light },
|
||||
{ name: i18nService.t("dark"), value: ThemeType.Dark },
|
||||
{ name: "Nord", value: ThemeType.Nord },
|
||||
{ name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark },
|
||||
];
|
||||
this.uriMatchOptions = [
|
||||
{ name: i18nService.t("baseDomain"), value: UriMatchStrategy.Domain },
|
||||
{ name: i18nService.t("host"), value: UriMatchStrategy.Host },
|
||||
|
@ -117,14 +101,8 @@ export class OptionsComponent implements OnInit {
|
|||
|
||||
this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$);
|
||||
|
||||
this.enableFavicon = await firstValueFrom(this.domainSettingsService.showFavicons$);
|
||||
|
||||
this.enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$);
|
||||
|
||||
this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$);
|
||||
|
||||
this.theme = await firstValueFrom(this.themeStateService.selectedTheme$);
|
||||
|
||||
const defaultUriMatch = await firstValueFrom(
|
||||
this.domainSettingsService.defaultUriMatchStrategy$,
|
||||
);
|
||||
|
@ -166,15 +144,6 @@ export class OptionsComponent implements OnInit {
|
|||
await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault);
|
||||
}
|
||||
|
||||
async updateFavicon() {
|
||||
await this.domainSettingsService.setShowFavicons(this.enableFavicon);
|
||||
}
|
||||
|
||||
async updateBadgeCounter() {
|
||||
await this.badgeSettingsService.setEnableBadgeCounter(this.enableBadgeCounter);
|
||||
this.messagingService.send("bgUpdateContextMenu");
|
||||
}
|
||||
|
||||
async updateShowCardsCurrentTab() {
|
||||
await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab);
|
||||
}
|
||||
|
@ -183,10 +152,6 @@ export class OptionsComponent implements OnInit {
|
|||
await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab);
|
||||
}
|
||||
|
||||
async saveTheme() {
|
||||
await this.themeStateService.setSelectedTheme(this.theme);
|
||||
}
|
||||
|
||||
async saveClearClipboard() {
|
||||
await this.autofillSettingsService.setClearClipboardDelay(this.clearClipboard);
|
||||
}
|
||||
|
|
|
@ -1,264 +0,0 @@
|
|||
<app-header>
|
||||
<div class="left">
|
||||
<app-pop-out></app-pop-out>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "settings" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right"></div>
|
||||
</app-header>
|
||||
<main tabindex="-1" [formGroup]="form">
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "manage" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/autofill"
|
||||
>
|
||||
<div class="row-main">{{ "autofill" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/folders"
|
||||
>
|
||||
<div class="row-main">{{ "folders" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/sync"
|
||||
>
|
||||
<div class="row-main">{{ "sync" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/excluded-domains"
|
||||
>
|
||||
<div class="row-main">{{ "excludedDomains" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "security" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
|
||||
<span *ngIf="policy.timeout && policy.action">
|
||||
{{
|
||||
"vaultTimeoutPolicyWithActionInEffect"
|
||||
| i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
|
||||
}}
|
||||
</span>
|
||||
<span *ngIf="policy.timeout && !policy.action">
|
||||
{{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }}
|
||||
</span>
|
||||
<span *ngIf="!policy.timeout && policy.action">
|
||||
{{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
|
||||
</span>
|
||||
</app-callout>
|
||||
<app-vault-timeout-input
|
||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||
[formControl]="form.controls.vaultTimeout"
|
||||
ngDefaultControl
|
||||
>
|
||||
</app-vault-timeout-input>
|
||||
<div class="box-content-row display-block" appBoxRow>
|
||||
<label for="vaultTimeoutAction">{{ "vaultTimeoutAction" | i18n }}</label>
|
||||
<select
|
||||
id="vaultTimeoutAction"
|
||||
name="VaultTimeoutActions"
|
||||
formControlName="vaultTimeoutAction"
|
||||
>
|
||||
<option *ngFor="let action of availableVaultTimeoutActions" [ngValue]="action">
|
||||
{{ action | i18n }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="!availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)"
|
||||
id="unlockMethodHelp"
|
||||
class="box-footer"
|
||||
>
|
||||
{{ "unlockMethodNeededToChangeTimeoutActionDesc" | i18n }}
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="pin">{{ "unlockWithPin" | i18n }}</label>
|
||||
<input id="pin" type="checkbox" formControlName="pin" />
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="supportsBiometric">
|
||||
<label for="biometric">{{ "unlockWithBiometrics" | i18n }}</label>
|
||||
<input id="biometric" type="checkbox" formControlName="biometric" />
|
||||
</div>
|
||||
<div
|
||||
class="box-content-row box-content-row-checkbox"
|
||||
appBoxRow
|
||||
*ngIf="supportsBiometric && this.form.value.biometric"
|
||||
>
|
||||
<label for="autoBiometricsPrompt">{{ "enableAutoBiometricsPrompt" | i18n }}</label>
|
||||
<input
|
||||
id="autoBiometricsPrompt"
|
||||
type="checkbox"
|
||||
(change)="updateAutoBiometricsPrompt()"
|
||||
formControlName="enableAutoBiometricsPrompt"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
*ngIf="
|
||||
!accountSwitcherEnabled && availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)
|
||||
"
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="lock()"
|
||||
>
|
||||
<div class="row-main">{{ "lockNow" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="twoStep()"
|
||||
>
|
||||
<div class="row-main">{{ "twoStepLogin" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "account" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<button type="button" class="box-content-row" routerLink="/premium">
|
||||
<div class="row-main">
|
||||
<div class="icon text-primary">
|
||||
<i class="bwi bwi-fw bwi-lg bwi-star-f" aria-hidden="true"></i>
|
||||
</div>
|
||||
<span class="text text-primary"
|
||||
><b>{{ "premiumMembership" | i18n }}</b></span
|
||||
>
|
||||
</div>
|
||||
<span><i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i></span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="changePassword()"
|
||||
*ngIf="showChangeMasterPass"
|
||||
>
|
||||
<div class="row-main">{{ "changeMasterPassword" | i18n }}</div>
|
||||
<i class="bwi bwi-external-link bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="fingerprint()"
|
||||
>
|
||||
<div class="row-main">{{ "fingerprintPhrase" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!accountSwitcherEnabled"
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="logOut()"
|
||||
>
|
||||
<div class="row-main">{{ "logOut" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "tools" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="import()"
|
||||
>
|
||||
<div class="row-main">{{ "importItems" | i18n }}</div>
|
||||
<i
|
||||
class="bwi bwi-external-link bwi-lg row-sub-icon bwi-rotate-270 bwi-fw"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="export()"
|
||||
>
|
||||
<div class="row-main">{{ "exportVault" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="webVault()"
|
||||
>
|
||||
<div class="row-main">{{ "bitWebVault" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "other" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/options"
|
||||
>
|
||||
<div class="row-main">{{ "options" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="about()"
|
||||
>
|
||||
<div class="row-main">{{ "about" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="share()"
|
||||
>
|
||||
<div class="row-main">{{ "learnOrg" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/help-and-feedback"
|
||||
>
|
||||
<div class="row-main">{{ "helpFeedback" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-describedby="rateExtensionHelp"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="rate()"
|
||||
>
|
||||
<div class="row-main">{{ "rateExtension" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="rateExtensionHelp" class="box-footer">{{ "rateExtensionDesc" | i18n }}</div>
|
||||
</div>
|
||||
</main>
|
|
@ -0,0 +1,11 @@
|
|||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-tabs-v2",
|
||||
template: `
|
||||
<popup-tab-navigation>
|
||||
<router-outlet></router-outlet>
|
||||
</popup-tab-navigation>
|
||||
`,
|
||||
})
|
||||
export class TabsV2Component {}
|
|
@ -1,5 +1,9 @@
|
|||
import { ImportService, ImportServiceAbstraction } from "@bitwarden/importer/core";
|
||||
|
||||
import {
|
||||
pinServiceFactory,
|
||||
PinServiceInitOptions,
|
||||
} from "../../../auth/background/service-factories/pin-service.factory";
|
||||
import {
|
||||
cryptoServiceFactory,
|
||||
CryptoServiceInitOptions,
|
||||
|
@ -36,7 +40,8 @@ export type ImportServiceInitOptions = ImportServiceFactoryOptions &
|
|||
ImportApiServiceInitOptions &
|
||||
I18nServiceInitOptions &
|
||||
CollectionServiceInitOptions &
|
||||
CryptoServiceInitOptions;
|
||||
CryptoServiceInitOptions &
|
||||
PinServiceInitOptions;
|
||||
|
||||
export function importServiceFactory(
|
||||
cache: {
|
||||
|
@ -56,6 +61,7 @@ export function importServiceFactory(
|
|||
await i18nServiceFactory(cache, opts),
|
||||
await collectionServiceFactory(cache, opts),
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await pinServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div bitDialogTitle>Bitwarden</div>
|
||||
<div bitDialogContent>
|
||||
<p>© Bitwarden Inc. 2015-{{ year }}</p>
|
||||
<p>{{ "version" | i18n }}: {{ version$ | async }}</p>
|
||||
<p class="user-select">{{ "version" | i18n }}: {{ version$ | async }}</p>
|
||||
<ng-container *ngIf="data$ | async as data">
|
||||
<p *ngIf="data.isCloud">
|
||||
{{ "serverVersion" | i18n }}: {{ data.serverConfig?.version }}
|
||||
|
@ -16,7 +16,7 @@
|
|||
|
||||
<ng-container *ngIf="!data.isCloud">
|
||||
<ng-container *ngIf="data.serverConfig.server">
|
||||
<p>
|
||||
<p class="user-select">
|
||||
{{ "serverVersion" | i18n }} <small>({{ "thirdParty" | i18n }})</small>:
|
||||
{{ data.serverConfig?.version }}
|
||||
<span *ngIf="!data.serverConfig.isValid()">
|
||||
|
@ -28,7 +28,7 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
|
||||
<p *ngIf="!data.serverConfig.server">
|
||||
<p class="user-select" *ngIf="!data.serverConfig.server">
|
||||
{{ "serverVersion" | i18n }} <small>({{ "selfHostedServer" | i18n }})</small>:
|
||||
{{ data.serverConfig?.version }}
|
||||
<span *ngIf="!data.serverConfig.isValid()">
|
|
@ -1,7 +1,7 @@
|
|||
<form (ngSubmit)="submit()" [formGroup]="exportForm">
|
||||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<button type="button" routerLink="/vault-settings">
|
||||
<span class="header-icon" aria-hidden="true"><i class="bwi bwi-angle-left"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<button type="button" routerLink="/vault-settings">
|
||||
<span class="header-icon" aria-hidden="true"><i class="bwi bwi-angle-left"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
<app-header>
|
||||
<div class="left">
|
||||
<app-pop-out></app-pop-out>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "settings" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right"></div>
|
||||
</app-header>
|
||||
<main tabindex="-1">
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "manage" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/account-security"
|
||||
>
|
||||
<div class="row-main">{{ "accountSecurity" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/autofill"
|
||||
>
|
||||
<div class="row-main">{{ "autofill" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/notifications"
|
||||
>
|
||||
<div class="row-main">{{ "notifications" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/vault-settings"
|
||||
>
|
||||
<div class="row-main">{{ "vault" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "account" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<button type="button" class="box-content-row" routerLink="/premium">
|
||||
<div class="row-main">
|
||||
<div class="icon text-primary">
|
||||
<i class="bwi bwi-fw bwi-lg bwi-star-f" aria-hidden="true"></i>
|
||||
</div>
|
||||
<span class="text text-primary"
|
||||
><b>{{ "premiumMembership" | i18n }}</b></span
|
||||
>
|
||||
</div>
|
||||
<span><i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "tools" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="webVault()"
|
||||
>
|
||||
<div class="row-main">{{ "bitWebVault" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "other" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/options"
|
||||
>
|
||||
<div class="row-main">{{ "options" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/appearance"
|
||||
>
|
||||
<div class="row-main">{{ "appearance" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="about()"
|
||||
>
|
||||
<div class="row-main">{{ "about" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="share()"
|
||||
>
|
||||
<div class="row-main">{{ "learnOrg" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/help-and-feedback"
|
||||
>
|
||||
<div class="row-main">{{ "helpFeedback" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-describedby="rateExtensionHelp"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="rate()"
|
||||
>
|
||||
<div class="row-main">{{ "rateExtension" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="rateExtensionHelp" class="box-footer">{{ "rateExtensionDesc" | i18n }}</div>
|
||||
</div>
|
||||
</main>
|
|
@ -0,0 +1,101 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom, Subject } from "rxjs";
|
||||
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
import { DeviceType } from "@bitwarden/common/enums";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
|
||||
|
||||
import { AboutComponent } from "./about/about.component";
|
||||
|
||||
const RateUrls = {
|
||||
[DeviceType.ChromeExtension]:
|
||||
"https://chromewebstore.google.com/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews",
|
||||
[DeviceType.FirefoxExtension]:
|
||||
"https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/#reviews",
|
||||
[DeviceType.OperaExtension]:
|
||||
"https://addons.opera.com/en/extensions/details/bitwarden-free-password-manager/#feedback-container",
|
||||
[DeviceType.EdgeExtension]:
|
||||
"https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh",
|
||||
[DeviceType.VivaldiExtension]:
|
||||
"https://chromewebstore.google.com/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews",
|
||||
[DeviceType.SafariExtension]: "https://apps.apple.com/app/bitwarden/id1352778147",
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: "tools-settings",
|
||||
templateUrl: "settings.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class SettingsComponent implements OnInit {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private vaultTimeoutService: VaultTimeoutService,
|
||||
public messagingService: MessagingService,
|
||||
private router: Router,
|
||||
private environmentService: EnvironmentService,
|
||||
private dialogService: DialogService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {}
|
||||
|
||||
async share() {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "learnOrg" },
|
||||
content: { key: "learnOrgConfirmation" },
|
||||
type: "info",
|
||||
});
|
||||
if (confirmed) {
|
||||
// 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
|
||||
BrowserApi.createNewTab("https://bitwarden.com/help/about-organizations/");
|
||||
}
|
||||
}
|
||||
|
||||
async webVault() {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const url = env.getWebVaultUrl();
|
||||
await BrowserApi.createNewTab(url);
|
||||
}
|
||||
|
||||
async import() {
|
||||
await this.router.navigate(["/import"]);
|
||||
if (await BrowserApi.isPopupOpen()) {
|
||||
// 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
|
||||
BrowserPopupUtils.openCurrentPagePopout(window);
|
||||
}
|
||||
}
|
||||
|
||||
export() {
|
||||
// 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.router.navigate(["/export"]);
|
||||
}
|
||||
|
||||
about() {
|
||||
this.dialogService.open(AboutComponent);
|
||||
}
|
||||
|
||||
rate() {
|
||||
const deviceType = this.platformUtilsService.getDevice();
|
||||
// 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
|
||||
BrowserApi.createNewTab((RateUrls as any)[deviceType]);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import { first } from "rxjs/operators";
|
|||
|
||||
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
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";
|
||||
|
@ -26,6 +27,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
|||
private route: ActivatedRoute,
|
||||
private location: Location,
|
||||
logService: LogService,
|
||||
configService: ConfigService,
|
||||
) {
|
||||
super(
|
||||
collectionService,
|
||||
|
@ -34,6 +36,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
|||
cipherService,
|
||||
organizationService,
|
||||
logService,
|
||||
configService,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<h1>Vault V2 Extension Refresh</h1>
|
|
@ -0,0 +1,13 @@
|
|||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-vault",
|
||||
templateUrl: "vault-v2.component.html",
|
||||
})
|
||||
export class VaultV2Component implements OnInit, OnDestroy {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "appearance" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right">
|
||||
<app-pop-out></app-pop-out>
|
||||
</div>
|
||||
</header>
|
||||
<main tabindex="-1">
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="theme">{{ "theme" | i18n }}</label>
|
||||
<select
|
||||
id="theme"
|
||||
name="Theme"
|
||||
aria-describedby="themeHelp"
|
||||
[(ngModel)]="theme"
|
||||
(change)="saveTheme()"
|
||||
>
|
||||
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="themeHelp" class="box-footer">
|
||||
{{ accountSwitcherEnabled ? ("themeDescAlt" | i18n) : ("themeDesc" | i18n) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="badge">{{ "enableBadgeCounter" | i18n }}</label>
|
||||
<input
|
||||
id="badge"
|
||||
type="checkbox"
|
||||
aria-describedby="badgeHelp"
|
||||
(change)="updateBadgeCounter()"
|
||||
[(ngModel)]="enableBadgeCounter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="badgeHelp" class="box-footer">{{ "badgeCounterDesc" | i18n }}</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="favicon">{{ "enableFavicon" | i18n }}</label>
|
||||
<input
|
||||
id="favicon"
|
||||
type="checkbox"
|
||||
aria-describedby="faviconHelp"
|
||||
(change)="updateFavicon()"
|
||||
[(ngModel)]="enableFavicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="faviconHelp" class="box-footer">
|
||||
{{ accountSwitcherEnabled ? ("faviconDescAlt" | i18n) : ("faviconDesc" | i18n) }}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
|
@ -0,0 +1,62 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { ThemeType } from "@bitwarden/common/platform/enums";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
|
||||
import { enableAccountSwitching } from "../../../platform/flags";
|
||||
|
||||
@Component({
|
||||
selector: "vault-appearance",
|
||||
templateUrl: "appearance.component.html",
|
||||
})
|
||||
export class AppearanceComponent implements OnInit {
|
||||
enableFavicon = false;
|
||||
enableBadgeCounter = true;
|
||||
theme: ThemeType;
|
||||
themeOptions: any[];
|
||||
accountSwitcherEnabled = false;
|
||||
|
||||
constructor(
|
||||
private messagingService: MessagingService,
|
||||
private domainSettingsService: DomainSettingsService,
|
||||
private badgeSettingsService: BadgeSettingsServiceAbstraction,
|
||||
i18nService: I18nService,
|
||||
private themeStateService: ThemeStateService,
|
||||
) {
|
||||
this.themeOptions = [
|
||||
{ name: i18nService.t("default"), value: ThemeType.System },
|
||||
{ name: i18nService.t("light"), value: ThemeType.Light },
|
||||
{ name: i18nService.t("dark"), value: ThemeType.Dark },
|
||||
{ name: "Nord", value: ThemeType.Nord },
|
||||
{ name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark },
|
||||
];
|
||||
|
||||
this.accountSwitcherEnabled = enableAccountSwitching();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.enableFavicon = await firstValueFrom(this.domainSettingsService.showFavicons$);
|
||||
|
||||
this.enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$);
|
||||
|
||||
this.theme = await firstValueFrom(this.themeStateService.selectedTheme$);
|
||||
}
|
||||
|
||||
async updateFavicon() {
|
||||
await this.domainSettingsService.setShowFavicons(this.enableFavicon);
|
||||
}
|
||||
|
||||
async updateBadgeCounter() {
|
||||
await this.badgeSettingsService.setEnableBadgeCounter(this.enableBadgeCounter);
|
||||
this.messagingService.send("bgUpdateContextMenu");
|
||||
}
|
||||
|
||||
async saveTheme() {
|
||||
await this.themeStateService.setSelectedTheme(this.theme);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<button type="button" routerLink="/vault-settings">
|
||||
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
|
@ -1,6 +1,6 @@
|
|||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<button type="button" routerLink="/vault-settings">
|
||||
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
|
@ -0,0 +1,56 @@
|
|||
<app-header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/tabs/settings">
|
||||
<span class="header-icon"><i class="bwi bwi-angle-left" aria-hidden="true"></i></span>
|
||||
<span>{{ "back" | i18n }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "vault" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right">
|
||||
<app-pop-out></app-pop-out>
|
||||
</div>
|
||||
</app-header>
|
||||
<main tabindex="-1">
|
||||
<div class="box list">
|
||||
<div class="box-content single-line">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/folders"
|
||||
>
|
||||
<div class="row-main">{{ "folders" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
appStopClick
|
||||
(click)="import()"
|
||||
>
|
||||
<div class="row-main">{{ "importItems" | i18n }}</div>
|
||||
<i
|
||||
class="bwi bwi-external-link bwi-lg row-sub-icon bwi-rotate-270 bwi-fw"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/export"
|
||||
>
|
||||
<div class="row-main">{{ "exportVault" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row box-content-row-flex text-default"
|
||||
routerLink="/sync"
|
||||
>
|
||||
<div class="row-main">{{ "sync" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
|
@ -0,0 +1,25 @@
|
|||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
|
||||
|
||||
@Component({
|
||||
selector: "vault-settings",
|
||||
templateUrl: "vault-settings.component.html",
|
||||
})
|
||||
export class VaultSettingsComponent {
|
||||
constructor(
|
||||
public messagingService: MessagingService,
|
||||
private router: Router,
|
||||
) {}
|
||||
|
||||
async import() {
|
||||
await this.router.navigate(["/import"]);
|
||||
if (await BrowserApi.isPopupOpen()) {
|
||||
await BrowserPopupUtils.openCurrentPagePopout(window);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -124,7 +124,7 @@
|
|||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
||||
</data>
|
||||
<data name="Description" xml:space="preserve">
|
||||
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
||||
<value>Recognised as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
||||
|
||||
SECURE YOUR DIGITAL LIFE
|
||||
Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access.
|
||||
|
@ -146,7 +146,7 @@ More reasons to choose Bitwarden:
|
|||
World-Class Encryption
|
||||
Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private.
|
||||
|
||||
3rd-party Audits
|
||||
3rd-Party Audits
|
||||
Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications.
|
||||
|
||||
Advanced 2FA
|
||||
|
@ -159,13 +159,13 @@ Built-in Generator
|
|||
Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy.
|
||||
|
||||
Global Translations
|
||||
Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin.
|
||||
Bitwarden translations exist for more than 60 languages, translated by the global community through Crowdin.
|
||||
|
||||
Cross-Platform Applications
|
||||
Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more.
|
||||
Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, desktop OS, and more.
|
||||
|
||||
Bitwarden secures more than just passwords
|
||||
End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev!
|
||||
End-to-end encrypted credential management solutions from Bitwarden empower organisations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev!
|
||||
</value>
|
||||
</data>
|
||||
<data name="AssetTitle" xml:space="preserve">
|
||||
|
|
|
@ -133,7 +133,7 @@ ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE
|
|||
Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions.
|
||||
|
||||
EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE
|
||||
Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features.
|
||||
Utilise Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features.
|
||||
|
||||
EMPOWER YOUR TEAMS WITH BITWARDEN
|
||||
Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more.
|
||||
|
@ -165,7 +165,7 @@ Cross-Platform Applications
|
|||
Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more.
|
||||
|
||||
Bitwarden secures more than just passwords
|
||||
End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev!
|
||||
End-to-end encrypted credential management solutions from Bitwarden empower organisations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev!
|
||||
</value>
|
||||
</data>
|
||||
<data name="AssetTitle" xml:space="preserve">
|
||||
|
|
|
@ -118,10 +118,10 @@
|
|||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Bitwarden Password Manager</value>
|
||||
<value>Bitwarden - Administrador de contraseñas</value>
|
||||
</data>
|
||||
<data name="Summary" xml:space="preserve">
|
||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
||||
<value>En casa, en el trabajo o en el viaje, Bitwarden asegura fácilmente todas sus contraseñas, claves de acceso e información confidencial.</value>
|
||||
</data>
|
||||
<data name="Description" xml:space="preserve">
|
||||
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
||||
|
@ -169,7 +169,7 @@ End-to-end encrypted credential management solutions from Bitwarden empower orga
|
|||
</value>
|
||||
</data>
|
||||
<data name="AssetTitle" xml:space="preserve">
|
||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
||||
<value>En casa, en el trabajo o mientras viaja, Bitwarden protege fácilmente todas sus contraseñas, claves de acceso e información confidencial.</value>
|
||||
</data>
|
||||
<data name="ScreenshotSync" xml:space="preserve">
|
||||
<value>Sincroniza y accede a tu caja fuerte desde múltiples dispositivos</value>
|
||||
|
|
|
@ -165,7 +165,7 @@ Aplicações multiplataforma
|
|||
Proteja e partilhe dados confidenciais dentro do seu cofre Bitwarden a partir de qualquer navegador, dispositivo móvel, ou SO de computador, e muito mais.
|
||||
|
||||
O Bitwarden protege mais do que apenas palavras-passe
|
||||
As soluções de gestão de credenciais encriptadas ponto a ponto do Bitwarden permitem que as organizações protejam tudo, incluindo segredos de programadores e experiências com chaves de acesso. Visite Bitwarden.com para saber mais sobre o Gestor de Segredos do Bitwarden e o Bitwarden Passwordless.dev!
|
||||
As soluções de gestão de credenciais encriptadas ponto a ponto do Bitwarden permitem que as organizações protejam tudo, incluindo segredos de programadores e experiências com chaves de acesso. Visite Bitwarden.com para saber mais sobre o Bitwarden - Gestor de Segredos e o Bitwarden Passwordless.dev!
|
||||
</value>
|
||||
</data>
|
||||
<data name="AssetTitle" xml:space="preserve">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@bitwarden/cli",
|
||||
"description": "A secure and free password manager for all of your devices.",
|
||||
"version": "2024.4.0",
|
||||
"version": "2024.5.0",
|
||||
"keywords": [
|
||||
"bitwarden",
|
||||
"password",
|
||||
|
|
|
@ -86,7 +86,7 @@ export class UnlockCommand {
|
|||
|
||||
if (passwordValid) {
|
||||
await this.masterPasswordService.setMasterKey(masterKey, userId);
|
||||
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
|
||||
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey);
|
||||
await this.cryptoService.setUserKey(userKey);
|
||||
|
||||
if (await this.keyConnectorService.getConvertAccountRequired()) {
|
||||
|
|
|
@ -10,8 +10,8 @@ import {
|
|||
AuthRequestService,
|
||||
LoginStrategyService,
|
||||
LoginStrategyServiceAbstraction,
|
||||
PinCryptoService,
|
||||
PinCryptoServiceAbstraction,
|
||||
PinService,
|
||||
PinServiceAbstraction,
|
||||
UserDecryptionOptionsService,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
|
@ -208,7 +208,7 @@ export class Main {
|
|||
cipherFileUploadService: CipherFileUploadService;
|
||||
keyConnectorService: KeyConnectorService;
|
||||
userVerificationService: UserVerificationService;
|
||||
pinCryptoService: PinCryptoServiceAbstraction;
|
||||
pinService: PinServiceAbstraction;
|
||||
stateService: StateService;
|
||||
autofillSettingsService: AutofillSettingsServiceAbstraction;
|
||||
domainSettingsService: DomainSettingsService;
|
||||
|
@ -360,11 +360,29 @@ export class Main {
|
|||
migrationRunner,
|
||||
);
|
||||
|
||||
this.masterPasswordService = new MasterPasswordService(this.stateProvider);
|
||||
this.masterPasswordService = new MasterPasswordService(
|
||||
this.stateProvider,
|
||||
this.stateService,
|
||||
this.keyGenerationService,
|
||||
this.encryptService,
|
||||
);
|
||||
|
||||
this.kdfConfigService = new KdfConfigService(this.stateProvider);
|
||||
|
||||
this.pinService = new PinService(
|
||||
this.accountService,
|
||||
this.cryptoFunctionService,
|
||||
this.encryptService,
|
||||
this.kdfConfigService,
|
||||
this.keyGenerationService,
|
||||
this.logService,
|
||||
this.masterPasswordService,
|
||||
this.stateProvider,
|
||||
this.stateService,
|
||||
);
|
||||
|
||||
this.cryptoService = new CryptoService(
|
||||
this.pinService,
|
||||
this.masterPasswordService,
|
||||
this.keyGenerationService,
|
||||
this.cryptoFunctionService,
|
||||
|
@ -486,6 +504,7 @@ export class Main {
|
|||
this.stateProvider,
|
||||
this.secureStorageService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
this.authRequestService = new AuthRequestService(
|
||||
|
@ -574,6 +593,8 @@ export class Main {
|
|||
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
|
||||
|
||||
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
|
||||
this.accountService,
|
||||
this.pinService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.cryptoService,
|
||||
this.tokenService,
|
||||
|
@ -582,14 +603,6 @@ export class Main {
|
|||
this.biometricStateService,
|
||||
);
|
||||
|
||||
this.pinCryptoService = new PinCryptoService(
|
||||
this.stateService,
|
||||
this.cryptoService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.logService,
|
||||
this.kdfConfigService,
|
||||
);
|
||||
|
||||
this.userVerificationService = new UserVerificationService(
|
||||
this.stateService,
|
||||
this.cryptoService,
|
||||
|
@ -598,7 +611,7 @@ export class Main {
|
|||
this.i18nService,
|
||||
this.userVerificationApiService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.pinCryptoService,
|
||||
this.pinService,
|
||||
this.logService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.platformUtilsService,
|
||||
|
@ -661,11 +674,13 @@ export class Main {
|
|||
this.i18nService,
|
||||
this.collectionService,
|
||||
this.cryptoService,
|
||||
this.pinService,
|
||||
);
|
||||
|
||||
this.individualExportService = new IndividualVaultExportService(
|
||||
this.folderService,
|
||||
this.cipherService,
|
||||
this.pinService,
|
||||
this.cryptoService,
|
||||
this.cryptoFunctionService,
|
||||
this.kdfConfigService,
|
||||
|
@ -674,6 +689,7 @@ export class Main {
|
|||
this.organizationExportService = new OrganizationVaultExportService(
|
||||
this.cipherService,
|
||||
this.apiService,
|
||||
this.pinService,
|
||||
this.cryptoService,
|
||||
this.cryptoFunctionService,
|
||||
this.collectionService,
|
||||
|
@ -685,6 +701,8 @@ export class Main {
|
|||
this.organizationExportService,
|
||||
);
|
||||
|
||||
this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.cryptoService);
|
||||
|
||||
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
|
||||
this.program = new Program(this);
|
||||
this.vaultProgram = new VaultProgram(this);
|
||||
|
@ -708,8 +726,6 @@ export class Main {
|
|||
);
|
||||
|
||||
this.providerApiService = new ProviderApiService(this.apiService);
|
||||
|
||||
this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.cryptoService);
|
||||
}
|
||||
|
||||
async run() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as chalk from "chalk";
|
||||
import { program, Command, OptionValues } from "commander";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
|
||||
|
@ -63,8 +64,16 @@ export class Program {
|
|||
process.env.BW_NOINTERACTION = "true";
|
||||
});
|
||||
|
||||
program.on("option:session", (key) => {
|
||||
program.on("option:session", async (key) => {
|
||||
process.env.BW_SESSION = key;
|
||||
|
||||
// once we have the session key, we can set the user key in memory
|
||||
const activeAccount = await firstValueFrom(this.main.accountService.activeAccount$);
|
||||
if (activeAccount) {
|
||||
await this.main.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(
|
||||
activeAccount.id,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
program.on("command:*", () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"dev_flags": {},
|
||||
"devFlags": {},
|
||||
"flags": {
|
||||
"multithreadDecryption": false,
|
||||
"enableCipherKeyEncryption": false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@bitwarden/desktop",
|
||||
"description": "A secure and free password manager for all of your devices.",
|
||||
"version": "2024.4.3",
|
||||
"version": "2024.5.0",
|
||||
"keywords": [
|
||||
"bitwarden",
|
||||
"password",
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { BehaviorSubject, firstValueFrom, Observable, Subject } from "rxjs";
|
||||
import { BehaviorSubject, Observable, Subject, firstValueFrom } from "rxjs";
|
||||
import { concatMap, debounceTime, filter, map, switchMap, takeUntil, tap } from "rxjs/operators";
|
||||
|
||||
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { AuthRequestServiceAbstraction, PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
|
@ -19,7 +20,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
|||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { ThemeType, KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
||||
import { KeySuffixOptions, ThemeType } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
@ -111,6 +112,7 @@ export class SettingsComponent implements OnInit {
|
|||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private policyService: PolicyService,
|
||||
private formBuilder: FormBuilder,
|
||||
private i18nService: I18nService,
|
||||
|
@ -127,6 +129,7 @@ export class SettingsComponent implements OnInit {
|
|||
private desktopSettingsService: DesktopSettingsService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
private desktopAutofillSettingsService: DesktopAutofillSettingsService,
|
||||
private pinService: PinServiceAbstraction,
|
||||
private authRequestService: AuthRequestServiceAbstraction,
|
||||
private logService: LogService,
|
||||
private nativeMessagingManifestService: NativeMessagingManifestService,
|
||||
|
@ -243,9 +246,10 @@ export class SettingsComponent implements OnInit {
|
|||
}),
|
||||
);
|
||||
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
|
||||
// Load initial values
|
||||
const pinStatus = await this.vaultTimeoutSettingsService.isPinLockSet();
|
||||
this.userHasPinSet = pinStatus !== "DISABLED";
|
||||
this.userHasPinSet = await this.pinService.isPinSet(userId);
|
||||
|
||||
const initialValues = {
|
||||
vaultTimeout: await this.vaultTimeoutSettingsService.getVaultTimeout(),
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom, map, Subject, takeUntil } from "rxjs";
|
||||
import { filter, firstValueFrom, map, Subject, takeUntil, timeout } from "rxjs";
|
||||
|
||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
|
@ -218,8 +218,10 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
await this.vaultTimeoutService.lock(message.userId);
|
||||
break;
|
||||
case "lockAllVaults": {
|
||||
const currentUser = await this.stateService.getUserId();
|
||||
const accounts = await firstValueFrom(this.stateService.accounts$);
|
||||
const currentUser = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a.id)),
|
||||
);
|
||||
const accounts = await firstValueFrom(this.accountService.accounts$);
|
||||
await this.vaultTimeoutService.lock(currentUser);
|
||||
for (const account of Object.keys(accounts)) {
|
||||
if (account === currentUser) {
|
||||
|
@ -564,19 +566,42 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
this.messagingService.send("updateAppMenu", { updateRequest: updateRequest });
|
||||
}
|
||||
|
||||
private async logOut(expired: boolean, userId?: string) {
|
||||
const userBeingLoggedOut =
|
||||
(userId as UserId) ??
|
||||
(await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))));
|
||||
// Even though the userId parameter is no longer optional doesn't mean a message couldn't be
|
||||
// passing null-ish values to us.
|
||||
private async logOut(expired: boolean, userId: UserId) {
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
|
||||
const userBeingLoggedOut = userId ?? activeUserId;
|
||||
|
||||
// Mark account as being cleaned up so that the updateAppMenu logic (executed on syncCompleted)
|
||||
// doesn't attempt to update a user that is being logged out as we will manually
|
||||
// call updateAppMenu when the logout is complete.
|
||||
this.startAccountCleanUp(userBeingLoggedOut);
|
||||
|
||||
let preLogoutActiveUserId;
|
||||
const nextUpAccount = await firstValueFrom(this.accountService.nextUpAccount$);
|
||||
const nextUpAccount =
|
||||
activeUserId === userBeingLoggedOut
|
||||
? await firstValueFrom(this.accountService.nextUpAccount$) // We'll need to switch accounts
|
||||
: null;
|
||||
|
||||
try {
|
||||
// HACK: We shouldn't wait for authentication status to change here but instead subscribe to the
|
||||
// authentication status to do various actions.
|
||||
const logoutPromise = firstValueFrom(
|
||||
this.authService.authStatusFor$(userBeingLoggedOut).pipe(
|
||||
filter((authenticationStatus) => authenticationStatus === AuthenticationStatus.LoggedOut),
|
||||
timeout({
|
||||
first: 5_000,
|
||||
with: () => {
|
||||
throw new Error(
|
||||
"The logout process did not complete in a reasonable amount of time.",
|
||||
);
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Provide the userId of the user to upload events for
|
||||
await this.eventUploadService.uploadEvents(userBeingLoggedOut);
|
||||
await this.syncService.setLastSync(new Date(0), userBeingLoggedOut);
|
||||
|
@ -590,26 +615,33 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
|
||||
await this.stateEventRunnerService.handleEvent("logout", userBeingLoggedOut);
|
||||
|
||||
preLogoutActiveUserId = this.activeUserId;
|
||||
await this.stateService.clean({ userId: userBeingLoggedOut });
|
||||
await this.accountService.clean(userBeingLoggedOut);
|
||||
|
||||
// HACK: Wait for the user logging outs authentication status to transition to LoggedOut
|
||||
await logoutPromise;
|
||||
} finally {
|
||||
this.finishAccountCleanUp(userBeingLoggedOut);
|
||||
}
|
||||
|
||||
if (nextUpAccount == null) {
|
||||
// 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.router.navigate(["login"]);
|
||||
} else if (preLogoutActiveUserId !== nextUpAccount.id) {
|
||||
this.messagingService.send("switchAccount", { userId: nextUpAccount.id });
|
||||
// We only need to change the display at all if the account being looked at is the one
|
||||
// being logged out. If it was a background account, no need to do anything.
|
||||
if (userBeingLoggedOut === activeUserId) {
|
||||
if (nextUpAccount != null) {
|
||||
this.messagingService.send("switchAccount", { userId: nextUpAccount.id });
|
||||
} else {
|
||||
// We don't have another user to switch to, bring them to the login page so they
|
||||
// can sign into a user.
|
||||
await this.accountService.switchAccount(null);
|
||||
void this.router.navigate(["login"]);
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateAppMenu();
|
||||
|
||||
// This must come last otherwise the logout will prematurely trigger
|
||||
// a process reload before all the state service user data can be cleaned up
|
||||
if (userBeingLoggedOut === preLogoutActiveUserId) {
|
||||
if (userBeingLoggedOut === activeUserId) {
|
||||
this.authService.logOut(async () => {
|
||||
if (expired) {
|
||||
this.platformUtilsService.showToast(
|
||||
|
@ -690,7 +722,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private async checkForSystemTimeout(timeout: number): Promise<void> {
|
||||
const accounts = await firstValueFrom(this.stateService.accounts$);
|
||||
const accounts = await firstValueFrom(this.accountService.accounts$);
|
||||
for (const userId in accounts) {
|
||||
if (userId == null) {
|
||||
continue;
|
||||
|
@ -700,7 +732,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
// 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
|
||||
options[1] === "logOut"
|
||||
? this.logOut(false, userId)
|
||||
? this.logOut(false, userId as UserId)
|
||||
: await this.vaultTimeoutService.lock(userId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,6 +92,11 @@ export class AccountSwitcherComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (!active.name && !active.email) {
|
||||
// We need to have this information at minimum to display them.
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: active.id,
|
||||
name: active.name,
|
||||
|
|
|
@ -59,6 +59,7 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/ge
|
|||
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { PinServiceAbstraction } from "../../../../../libs/auth/src/common/abstractions";
|
||||
import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service";
|
||||
import { Account } from "../../models/account";
|
||||
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
|
||||
|
@ -183,6 +184,7 @@ const safeProviders: SafeProvider[] = [
|
|||
provide: SystemServiceAbstraction,
|
||||
useClass: SystemService,
|
||||
deps: [
|
||||
PinServiceAbstraction,
|
||||
MessagingServiceAbstraction,
|
||||
PlatformUtilsServiceAbstraction,
|
||||
RELOAD_CALLBACK,
|
||||
|
@ -221,7 +223,7 @@ const safeProviders: SafeProvider[] = [
|
|||
safeProvider({
|
||||
provide: EncryptedMessageHandlerService,
|
||||
deps: [
|
||||
StateServiceAbstraction,
|
||||
AccountServiceAbstraction,
|
||||
AuthServiceAbstraction,
|
||||
CipherServiceAbstraction,
|
||||
PolicyServiceAbstraction,
|
||||
|
@ -250,6 +252,7 @@ const safeProviders: SafeProvider[] = [
|
|||
provide: CryptoServiceAbstraction,
|
||||
useClass: ElectronCryptoService,
|
||||
deps: [
|
||||
PinServiceAbstraction,
|
||||
InternalMasterPasswordServiceAbstraction,
|
||||
KeyGenerationServiceAbstraction,
|
||||
CryptoFunctionServiceAbstraction,
|
||||
|
|
|
@ -12,8 +12,16 @@
|
|||
<input class="tw-font-mono" bitInput type="password" formControlName="pin" />
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
</bit-form-field>
|
||||
<label class="tw-flex tw-items-start tw-gap-2" *ngIf="showMasterPassOnRestart">
|
||||
<input class="tw-mt-1" type="checkbox" bitCheckbox formControlName="masterPassOnRestart" />
|
||||
<label
|
||||
class="tw-flex tw-items-start tw-gap-2"
|
||||
*ngIf="showMasterPasswordOnClientRestartOption"
|
||||
>
|
||||
<input
|
||||
class="tw-mt-1"
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
formControlName="requireMasterPasswordOnClientRestart"
|
||||
/>
|
||||
<span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { of } from "rxjs";
|
|||
|
||||
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
|
@ -155,8 +155,8 @@ describe("LockComponent", () => {
|
|||
useValue: mock<UserVerificationService>(),
|
||||
},
|
||||
{
|
||||
provide: PinCryptoServiceAbstraction,
|
||||
useValue: mock<PinCryptoServiceAbstraction>(),
|
||||
provide: PinServiceAbstraction,
|
||||
useValue: mock<PinServiceAbstraction>(),
|
||||
},
|
||||
{
|
||||
provide: BiometricStateService,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue