From 068c63e891f2d6e396c86bef1f2668e77ac1485f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 22 May 2025 15:05:28 +0200 Subject: [PATCH 01/41] Fix send rotation broken due to incorrect types (#14874) --- libs/common/src/tools/send/services/send.service.spec.ts | 6 ++---- libs/common/src/tools/send/services/send.service.ts | 5 +++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 65fd53edd75..611cc9c7b76 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -477,11 +477,9 @@ describe("SendService", () => { let encryptedKey: EncString; beforeEach(() => { - encryptService.unwrapSymmetricKey.mockResolvedValue( - new SymmetricCryptoKey(new Uint8Array(32)), - ); + encryptService.decryptBytes.mockResolvedValue(new Uint8Array(16)); encryptedKey = new EncString("Re-encrypted Send Key"); - encryptService.wrapSymmetricKey.mockResolvedValue(encryptedKey); + encryptService.encryptBytes.mockResolvedValue(encryptedKey); }); it("returns re-encrypted user sends", async () => { diff --git a/libs/common/src/tools/send/services/send.service.ts b/libs/common/src/tools/send/services/send.service.ts index db3834789c8..3a5bcbe997b 100644 --- a/libs/common/src/tools/send/services/send.service.ts +++ b/libs/common/src/tools/send/services/send.service.ts @@ -292,8 +292,9 @@ export class SendService implements InternalSendServiceAbstraction { ) { const requests = await Promise.all( sends.map(async (send) => { - const sendKey = await this.encryptService.unwrapSymmetricKey(send.key, originalUserKey); - send.key = await this.encryptService.wrapSymmetricKey(sendKey, rotateUserKey); + // Send key is not a key but a 16 byte seed used to derive the key + const sendKey = await this.encryptService.decryptBytes(send.key, originalUserKey); + send.key = await this.encryptService.encryptBytes(sendKey, rotateUserKey); return new SendWithIdRequest(send); }), ); From 46a0b709fe5b17027b571affbcbc82eb58f61745 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Thu, 22 May 2025 09:15:30 -0400 Subject: [PATCH 02/41] [PM-21644] Cannot retrieve attachment from bw serve (#14806) * Modified saveAttachmenttofIle to implement callback attachment content decryption * renamed parameter --- apps/cli/src/commands/download.command.ts | 9 +++---- apps/cli/src/commands/get.command.ts | 26 ++++++++++++------- .../tools/send/commands/receive.command.ts | 9 ++++++- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/apps/cli/src/commands/download.command.ts b/apps/cli/src/commands/download.command.ts index 472b084f5d7..2c75617ca5b 100644 --- a/apps/cli/src/commands/download.command.ts +++ b/apps/cli/src/commands/download.command.ts @@ -2,8 +2,6 @@ // @ts-strict-ignore import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { Response } from "../models/response"; import { FileResponse } from "../models/response/file.response"; @@ -25,15 +23,15 @@ export abstract class DownloadCommand { /** * Fetches an attachment via the url, decrypts it's content and saves it to a file * @param url - url used to retrieve the attachment - * @param key - SymmetricCryptoKey to decrypt the file contents * @param fileName - filename used when written to disk + * @param decrypt - Function used to decrypt the response * @param output - If output is empty or `--raw` was passed to the initial command the content is output onto stdout * @returns Promise */ protected async saveAttachmentToFile( url: string, - key: SymmetricCryptoKey, fileName: string, + decrypt: (resp: globalThis.Response) => Promise, output?: string, ) { const response = await this.apiService.nativeFetch( @@ -46,8 +44,7 @@ export abstract class DownloadCommand { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const decBuf = await decrypt(response); if (process.env.BW_SERVE === "true") { const res = new FileResponse(Buffer.from(decBuf), fileName); return Response.success(res); diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 60be0a8d2cb..8554f8e2ae1 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -27,7 +27,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; @@ -345,12 +345,11 @@ export class GetCommand extends DownloadCommand { return Response.multipleResults(attachments.map((a) => a.id)); } - const account = await firstValueFrom(this.accountService.activeAccount$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$(account.id), + this.accountProfileService.hasPremiumFromAnySource$(activeUserId), ); if (!canAccessPremium) { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const originalCipher = await this.cipherService.get(cipher.id, activeUserId); if (originalCipher == null || originalCipher.organizationId == null) { return Response.error("Premium status is required to use this feature."); @@ -374,11 +373,20 @@ export class GetCommand extends DownloadCommand { } } - const key = - attachments[0].key != null - ? attachments[0].key - : await this.keyService.getOrgKey(cipher.organizationId); - return await this.saveAttachmentToFile(url, key, attachments[0].fileName, options.output); + const decryptBufferFn = (resp: globalThis.Response) => + this.cipherService.getDecryptedAttachmentBuffer( + cipher.id as CipherId, + attachments[0], + resp, + activeUserId, + ); + + return await this.saveAttachmentToFile( + url, + attachments[0].fileName, + decryptBufferFn, + options.output, + ); } private async getFolder(id: string) { diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index c67b4213d97..a412f7c1667 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -11,6 +11,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendAccess } from "@bitwarden/common/tools/send/models/domain/send-access"; @@ -98,10 +99,16 @@ export class SendReceiveCommand extends DownloadCommand { this.sendAccessRequest, apiUrl, ); + + const decryptBufferFn = async (resp: globalThis.Response) => { + const encBuf = await EncArrayBuffer.fromResponse(resp); + return this.encryptService.decryptFileData(encBuf, this.decKey); + }; + return await this.saveAttachmentToFile( downloadData.url, - this.decKey, response?.file?.fileName, + decryptBufferFn, options.output, ); } From 9417d8a94328ce636c55dcbe987ed3e7b1846938 Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Thu, 22 May 2025 09:35:39 -0400 Subject: [PATCH 03/41] [PM-18633] Remove feature flagged logic (#14856) * remove feature flagged logic * clean up --- .../manage/group-add-edit.component.html | 2 +- .../manage/group-add-edit.component.ts | 9 +++--- .../member-dialog.component.html | 4 +-- .../member-dialog/member-dialog.component.ts | 24 +++++--------- .../collection-dialog.component.html | 2 +- .../collection-dialog.component.ts | 31 +++---------------- libs/common/src/enums/feature-flag.enum.ts | 2 -- 7 files changed, 21 insertions(+), 53 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html index 5c8c0c07f88..101512dea04 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html @@ -23,7 +23,7 @@ {{ "characterMaximum" | i18n: 100 }} - + {{ "externalId" | i18n }} {{ "externalIdDesc" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index 53a6a3cf196..ca7d07220b2 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -28,7 +28,6 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -147,6 +146,10 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { return this.params.organizationId; } + protected get isExternalIdVisible(): boolean { + return !!this.groupForm.get("externalId")?.value; + } + protected get editMode(): boolean { return this.groupId != null; } @@ -227,10 +230,6 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { this.groupDetails$, ]).pipe(map(([allowAdminAccess, groupDetails]) => !allowAdminAccess && groupDetails != null)); - protected isExternalIdVisible$ = this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe(map((isEnabled) => !isEnabled || !!this.groupForm.get("externalId")?.value)); - constructor( @Inject(DIALOG_DATA) private params: GroupAddEditDialogParams, private dialogRef: DialogRef, diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html index 9564952f511..fa0a7bd85a3 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html @@ -177,13 +177,13 @@ - + {{ "externalId" | i18n }} {{ "externalIdDesc" | i18n }} - + {{ "ssoExternalId" | i18n }} {{ "ssoExternalIdDesc" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 10bbc5cfe52..9adfb1db3f2 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -157,28 +157,20 @@ export class MemberDialogComponent implements OnDestroy { manageResetPassword: false, }); - protected isExternalIdVisible$ = this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe( - map((isEnabled) => { - return !isEnabled || !!this.formGroup.get("externalId")?.value; - }), - ); + get isExternalIdVisible(): boolean { + return !!this.formGroup.get("externalId")?.value; + } - protected isSsoExternalIdVisible$ = this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe( - map((isEnabled) => { - return isEnabled && !!this.formGroup.get("ssoExternalId")?.value; - }), - ); - - private destroy$ = new Subject(); + get isSsoExternalIdVisible(): boolean { + return !!this.formGroup.get("ssoExternalId")?.value; + } get customUserTypeSelected(): boolean { return this.formGroup.value.type === OrganizationUserType.Custom; } + private destroy$ = new Subject(); + isEditDialogParams( params: EditMemberDialogParams | AddMemberDialogParams, ): params is EditMemberDialogParams { diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html index 12d7a920a2d..4a91fcc2a41 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html @@ -35,7 +35,7 @@ - + {{ "externalId" | i18n }} {{ "externalIdDesc" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts index 07bff3aba64..70b26041df6 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts @@ -38,7 +38,6 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DIALOG_DATA, @@ -135,7 +134,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { protected showOrgSelector = false; protected formGroup = this.formBuilder.group({ name: ["", [Validators.required, BitValidators.forbiddenCharacters(["/"])]], - externalId: "", + externalId: { value: "", disabled: true }, parent: undefined as string | undefined, access: [[] as AccessItemValue[]], selectedOrg: "", @@ -145,16 +144,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { protected showAddAccessWarning = false; protected collections: Collection[]; protected buttonDisplayName: ButtonType = ButtonType.Save; - protected isExternalIdVisible$ = this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe( - map((isEnabled) => { - return ( - !isEnabled || - (!!this.params.isAdminConsoleActive && !!this.formGroup.get("externalId")?.value) - ); - }), - ); private orgExceedingCollectionLimit!: Organization; constructor( @@ -165,7 +154,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private groupService: GroupApiService, private collectionAdminService: CollectionAdminService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, private organizationUserApiService: OrganizationUserApiService, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, @@ -354,6 +342,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { return this.formGroup.controls.selectedOrg; } + protected get isExternalIdVisible(): boolean { + return this.params.isAdminConsoleActive && !!this.formGroup.get("externalId")?.value; + } + protected get collectionId() { return this.params.collectionId; } @@ -490,23 +482,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private handleFormGroupReadonly(readonly: boolean) { if (readonly) { this.formGroup.controls.name.disable(); - this.formGroup.controls.externalId.disable(); this.formGroup.controls.parent.disable(); this.formGroup.controls.access.disable(); } else { this.formGroup.controls.name.enable(); - - this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe(takeUntil(this.destroy$)) - .subscribe((isEnabled) => { - if (isEnabled) { - this.formGroup.controls.externalId.disable(); - } else { - this.formGroup.controls.externalId.enable(); - } - }); - this.formGroup.controls.parent.enable(); this.formGroup.controls.access.enable(); } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index f9e68efe4be..dcd214c37d7 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -12,7 +12,6 @@ import { ServerConfig } from "../platform/abstractions/config/server-config"; export enum FeatureFlag { /* Admin Console Team */ LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission", - SsoExternalIdVisibility = "pm-18630-sso-external-id-visibility", AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner", SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions", @@ -83,7 +82,6 @@ const FALSE = false as boolean; export const DefaultFeatureFlagValue = { /* Admin Console Team */ [FeatureFlag.LimitItemDeletion]: FALSE, - [FeatureFlag.SsoExternalIdVisibility]: FALSE, [FeatureFlag.AccountDeprovisioningBanner]: FALSE, [FeatureFlag.SeparateCustomRolePermissions]: FALSE, From f52e4e27a030cb5ba9cc743f088c61bbebcbcc72 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Thu, 22 May 2025 11:09:33 -0500 Subject: [PATCH 04/41] [PM-12770] Assign to Collections Hint (#14529) * allow use of common spec in lib/vault tests * pass readonly collections to the assign collection component - The assign to collections component filters them out already. -They're also needed to display copy within the component * add hint to assign to collections component when there are read only collections assigned to a cipher already * add readonly hint to desktop * only show collection hint for collections that are assigned to the provided ciphers * consider admin/owner edit everything permission when assigning to collections * fix icon in test --- .../assign-collections.component.ts | 2 +- apps/desktop/src/locales/en/messages.json | 9 ++ .../collections/vault.component.ts | 3 +- .../vault/individual-vault/vault.component.ts | 2 +- libs/vault/jest.config.js | 10 +- .../assign-collections.component.html | 5 +- .../assign-collections.component.spec.ts | 113 ++++++++++++++++++ .../assign-collections.component.ts | 29 +++++ 8 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 libs/vault/src/components/assign-collections.component.spec.ts diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index 7052be5ea62..a11a7d806bd 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -74,7 +74,7 @@ export class AssignCollections { combineLatest([cipher$, this.collectionService.decryptedCollections$]) .pipe(takeUntilDestroyed(), first()) .subscribe(([cipherView, collections]) => { - let availableCollections = collections.filter((c) => !c.readOnly); + let availableCollections = collections; const organizationId = (cipherView?.organizationId as OrganizationId) ?? null; // If the cipher is already a part of an organization, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 35433e8e2e1..50b9ed4336c 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 96c00faceb2..a3b62838d6a 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -362,8 +362,7 @@ export class VaultComponent implements OnInit, OnDestroy { if (this.organization.canEditAllCiphers) { return collections; } - // The user is only allowed to add/edit items to assigned collections that are not readonly - return collections.filter((c) => c.assigned && !c.readOnly); + return collections.filter((c) => c.assigned); }), shareReplay({ refCount: true, bufferSize: 1 }), ); diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 55bbd0c0651..6e751f600dc 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -933,7 +933,7 @@ export class VaultComponent implements OnInit, OnDestroy { if (orgId && orgId !== "MyVault") { const organization = this.allOrganizations.find((o) => o.id === orgId); availableCollections = this.allCollections.filter( - (c) => c.organizationId === organization.id && !c.readOnly, + (c) => c.organizationId === organization.id, ); } diff --git a/libs/vault/jest.config.js b/libs/vault/jest.config.js index e33c115e8dd..16db37527ac 100644 --- a/libs/vault/jest.config.js +++ b/libs/vault/jest.config.js @@ -10,7 +10,11 @@ module.exports = { displayName: "libs/vault tests", preset: "jest-preset-angular", setupFilesAfterEnv: ["/test.setup.ts"], - moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "/", - }), + moduleNameMapper: pathsToModuleNameMapper( + // lets us use @bitwarden/common/spec in tests + { "@bitwarden/common/spec": ["../common/spec"], ...(compilerOptions?.paths ?? {}) }, + { + prefix: "/", + }, + ), }; diff --git a/libs/vault/src/components/assign-collections.component.html b/libs/vault/src/components/assign-collections.component.html index d68799eec6d..a82f2cb29a1 100644 --- a/libs/vault/src/components/assign-collections.component.html +++ b/libs/vault/src/components/assign-collections.component.html @@ -37,13 +37,16 @@
- + {{ "selectCollectionsToAssign" | i18n }} + + {{ "cannotRemoveViewOnlyCollections" | i18n: readOnlyCollectionNames.join(", ") }} +
diff --git a/libs/vault/src/components/assign-collections.component.spec.ts b/libs/vault/src/components/assign-collections.component.spec.ts new file mode 100644 index 00000000000..d6707cd1064 --- /dev/null +++ b/libs/vault/src/components/assign-collections.component.spec.ts @@ -0,0 +1,113 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { ProductTierType } from "@bitwarden/common/billing/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { ToastService } from "@bitwarden/components"; + +import { + AssignCollectionsComponent, + CollectionAssignmentParams, +} from "./assign-collections.component"; + +describe("AssignCollectionsComponent", () => { + let component: AssignCollectionsComponent; + let fixture: ComponentFixture; + + const mockUserId = "mock-user-id" as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + + const editCollection = new CollectionView(); + editCollection.id = "collection-id" as CollectionId; + editCollection.organizationId = "org-id" as OrganizationId; + editCollection.name = "Editable Collection"; + editCollection.readOnly = false; + editCollection.manage = true; + + const readOnlyCollection1 = new CollectionView(); + readOnlyCollection1.id = "read-only-collection-id" as CollectionId; + readOnlyCollection1.organizationId = "org-id" as OrganizationId; + readOnlyCollection1.name = "Read Only Collection"; + readOnlyCollection1.readOnly = true; + + const readOnlyCollection2 = new CollectionView(); + readOnlyCollection2.id = "read-only-collection-id-2" as CollectionId; + readOnlyCollection2.organizationId = "org-id" as OrganizationId; + readOnlyCollection2.name = "Read Only Collection 2"; + readOnlyCollection2.readOnly = true; + + const params = { + organizationId: "org-id" as OrganizationId, + ciphers: [ + { + id: "cipher-id", + name: "Cipher Name", + collectionIds: [readOnlyCollection1.id], + edit: true, + } as unknown as CipherView, + ], + availableCollections: [editCollection, readOnlyCollection1, readOnlyCollection2], + } as CollectionAssignmentParams; + + const org = { + id: "org-id", + name: "Test Org", + productTierType: ProductTierType.Enterprise, + } as Organization; + + const organizations$ = jest.fn().mockReturnValue(of([org])); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + providers: [ + { provide: CipherService, useValue: mock() }, + { provide: OrganizationService, useValue: mock({ organizations$ }) }, + { provide: CollectionService, useValue: mock() }, + { provide: ToastService, useValue: mock() }, + { provide: AccountService, useValue: accountService }, + { provide: I18nService, useValue: { t: (...keys: string[]) => keys.join(" ") } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(AssignCollectionsComponent); + component = fixture.componentInstance; + component.params = params; + fixture.detectChanges(); + }); + + describe("read only collections", () => { + beforeEach(async () => { + await component.ngOnInit(); + fixture.detectChanges(); + }); + + it("shows read-only hint for assigned collections", () => { + const hint = fixture.debugElement.query(By.css('[data-testid="view-only-hint"]')); + + expect(hint.nativeElement.textContent.trim()).toBe( + "cannotRemoveViewOnlyCollections Read Only Collection", + ); + }); + + it("does not show read only collections in the list", () => { + expect(component["availableCollections"]).toEqual([ + { + icon: "bwi-collection-shared", + id: editCollection.id, + labelName: editCollection.name, + listName: editCollection.name, + }, + ]); + }); + }); +}); diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index 6a0c45cfbe3..db62f096faa 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -126,6 +126,12 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI collections: [[], [Validators.required]], }); + /** + * Collections that are already assigned to the cipher and are read-only. These cannot be removed. + * @protected + */ + protected readOnlyCollectionNames: string[] = []; + protected totalItemCount: number; protected editableItemCount: number; protected readonlyItemCount: number; @@ -301,6 +307,8 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI this.organizationService.organizations$(userId).pipe(getOrganizationById(organizationId)), ); + await this.setReadOnlyCollectionNames(); + this.availableCollections = this.params.availableCollections .filter((collection) => { return collection.canEditItems(org); @@ -503,4 +511,25 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI await this.cipherService.saveCollectionsWithServer(cipher, userId); } } + + /** + * Only display collections that are read-only and are assigned to the ciphers. + */ + private async setReadOnlyCollectionNames() { + const { availableCollections, ciphers } = this.params; + + const organization = await firstValueFrom( + this.organizations$.pipe(map((orgs) => orgs.find((o) => o.id === this.selectedOrgId))), + ); + + this.readOnlyCollectionNames = availableCollections + .filter((c) => { + return ( + c.readOnly && + ciphers.some((cipher) => cipher.collectionIds.includes(c.id)) && + !c.canEditItems(organization) + ); + }) + .map((c) => c.name); + } } From 753e7af380b53ec111bad58e13b442a713c6db4c Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Thu, 22 May 2025 12:29:15 -0400 Subject: [PATCH 05/41] [PM-21882] Lit Components Cleanup (#14872) * rename item row component to cipher item row * move CipherItem into CipherItemRow instead of passing a generic children prop * remove redundant embedded stories * add mock data to dropdown button story --- .../lit-stories/.lit-docs/cipher-action.mdx | 83 ----------------- .../lit-stories/.lit-docs/cipher-icon.mdx | 90 ------------------- .../.lit-docs/cipher-indicator-icon.mdx | 81 ----------------- .../ciphers/cipher-action.lit-stories.ts | 31 ------- .../ciphers/cipher-icon.lit-stories.ts | 33 ------- .../cipher-indicator-icons.lit-stories.ts | 28 ------ .../ciphers/cipher-info.lit-stories.ts | 23 ----- .../rows/button-row.lit-stories.ts | 51 +++++++++++ .../cipher-item-row.lit-stories.ts} | 20 ++--- .../lit-stories/rows/item-row.lit-stories.ts | 22 ----- .../content/components/notification/body.ts | 16 ++-- .../components/rows/cipher-item-row.ts | 78 ++++++++++++++++ .../content/components/rows/item-row.ts | 55 ------------ 13 files changed, 145 insertions(+), 466 deletions(-) delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-action.mdx delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-icon.mdx delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icons.lit-stories.ts delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-info.lit-stories.ts rename apps/browser/src/autofill/content/components/lit-stories/{ciphers/cipher-item.lit-stories.ts => rows/cipher-item-row.lit-stories.ts} (62%) delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/rows/cipher-item-row.ts delete mode 100644 apps/browser/src/autofill/content/components/rows/item-row.ts diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-action.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-action.mdx deleted file mode 100644 index 3b5dcd8797a..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-action.mdx +++ /dev/null @@ -1,83 +0,0 @@ -import { Meta, Controls, Primary } from "@storybook/addon-docs"; - -import * as stories from "./cipher-action.lit-stories"; - - - -## Cipher Action - -The `CipherAction` component is a functional UI element that handles actions related to ciphers in a -secure environment. Built with the `lit` library and styled for consistency across themes, it -provides flexibility and accessibility while supporting various notification types. - - - - -## Props - -| **Prop** | **Type** | **Required** | **Description** | -| ------------------ | --------------------------------------------------- | ------------ | -------------------------------------------------------------- | -| `handleAction` | `(e: Event) => void` | No | Function to execute when an action is triggered. | -| `notificationType` | `NotificationTypes.Change \| NotificationTypes.Add` | Yes | Specifies the type of notification associated with the action. | -| `theme` | `Theme` | Yes | The theme to style the component. Must match the `Theme` enum. | - -## Installation and Setup - -1. Ensure the necessary dependencies are installed: - - - `lit`: Used to render the component. - -2. Pass the required props when rendering the component: - - `handleAction`: Optional function to handle the triggered action. - - `notificationType`: Mandatory type from `NotificationTypes` to define the action context. - - `theme`: The styling theme (must be a valid `Theme` enum value). - -## Accessibility (WCAG) Compliance - -The `CipherAction` component is designed to be accessible, ensuring usability across diverse user -bases. Below are the key considerations for accessibility: - -### Keyboard Accessibility - -- Fully navigable using the keyboard. -- The action can be triggered using the `Enter` or `Space` key for users relying on keyboard - interaction. - -### Screen Reader Compatibility - -- The semantic elements used in the `CipherAction` component ensure that assistive technologies can - interpret the component correctly. -- Text associated with the `notificationType` is programmatically linked, providing clarity for - screen reader users. - -### Focus Management - -- The component includes focus styles to ensure visibility during navigation. -- Proper focus management ensures the component works seamlessly with keyboard navigation. - -### Visual Feedback - -- Provides distinct visual states for different themes and states: - - **Hover:** Adjustments to background, border, and text for enhanced visibility. - - **Active:** Highlights the button with a focus state when activated. - - **Disabled:** Grays out the component to indicate inactivity. - -## Usage Example - -Here's an example of how to integrate the `CipherAction` component: - -```ts -import { CipherAction } from "../../cipher/cipher-action"; -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; -import { NotificationTypes } from "../../../../notification/abstractions/notification-bar"; - -const handleAction = (e: Event) => { - console.log("Cipher action triggered!", e); -}; - -; -``` diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-icon.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-icon.mdx deleted file mode 100644 index a1a94efde7b..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-icon.mdx +++ /dev/null @@ -1,90 +0,0 @@ -import { Meta, Controls, Primary } from "@storybook/addon-docs"; - -import * as stories from "./cipher-icon.lit-stories"; - - - -## Cipher Icon - -The `CipherIcon` component is a versatile icon renderer designed for secure environments. It -dynamically supports custom icons provided via URIs or displays a default icon (`Globe`) styled -based on the theme and provided properties. - - - - -## Props - -| **Prop** | **Type** | **Required** | **Description** | -| -------- | ------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `color` | `string` | Yes | A contextual color override applied when the `uri` is not provided, ensuring consistent styling of the default icon. | -| `size` | `string` | Yes | A valid CSS `width` value representing the width basis of the graphic. The height adjusts to maintain the original aspect ratio of the graphic. | -| `theme` | `Theme` | Yes | The styling theme for the icon, matching the `Theme` enum. | -| `uri` | `string` (optional) | No | A URL to an external graphic. If provided, the component displays this icon. If omitted, a default icon (`Globe`) styled with the provided `color` and `theme`. | - -## Installation and Setup - -1. Ensure the necessary dependencies are installed: - - - `lit`: Renders the component. - - `@emotion/css`: Styles the component. - -2. Pass the necessary props when using the component: - - `color`: Used when no `uri` is provided to style the default icon. - - `size`: Defines the width of the icon. Height maintains aspect ratio. - - `theme`: Specifies the theme for styling. - - `uri` (optional): If provided, this URI is used to display a custom icon. - -## Accessibility (WCAG) Compliance - -The `CipherIcon` component ensures accessible and user-friendly interactions through thoughtful -design: - -### Semantic Rendering - -- When the `uri` is provided, the component renders an `` element, which is semantically - appropriate for external graphics. -- If no `uri` is provided, the default icon is wrapped in a ``, ensuring proper context for - screen readers. - -### Visual Feedback - -- The component visually adjusts based on the `size`, `color`, and `theme`, ensuring the icon - remains clear and legible across different environments. - -### Keyboard and Screen Reader Support - -- Ensure that any container or parent component provides appropriate `alt` text or labeling when - `uri` is used with an `` tag for additional accessibility. - -## Usage Example - -Here's an example of how to integrate the `CipherIcon` component: - -```ts -import { CipherIcon } from "./cipher-icon"; -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -; -``` - -This configuration displays a custom icon from the provided URI with a width of 32px, styled for the -light theme. If the URI is omitted, the Globe icon is used as the fallback, colored in blue. - -### Default Styles - -- The default styles ensure responsive and clean design: - -- Width: Defined by the size prop. -- Height: Automatically adjusts to maintain the aspect ratio. -- Fit Content: Ensures the icon does not overflow or distort its container. - -### Notes - -- Always validate the uri provided to ensure it points to a secure and accessible location. -- Use the color and theme props for consistent fallback styling when uri is not provided. diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx deleted file mode 100644 index 6c338276c02..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx +++ /dev/null @@ -1,81 +0,0 @@ -import { Meta, Controls, Primary } from "@storybook/addon-docs"; - -import * as stories from "./cipher-indicator-icon.lit-stories"; - - - -## Cipher Info Indicator Icons - -The `CipherInfoIndicatorIcons` component displays a set of icons indicating specific attributes -related to cipher information. It supports business and family organization indicators, styled -dynamically based on the provided theme. - - - - -## Props - -| **Prop** | **Type** | **Required** | **Description** | -| ------------------ | --------- | ------------ | ----------------------------------------------------------------------- | -| `showBusinessIcon` | `boolean` | No | Displays the business organization icon when set to `true`. | -| `showFamilyIcon` | `boolean` | No | Displays the family organization icon when set to `true`. | -| `theme` | `Theme` | Yes | Defines the theme used to style the icons. Must match the `Theme` enum. | - -## Installation and Setup - -1. Ensure the necessary dependencies are installed: - - - `lit`: Renders the component. - - `@emotion/css`: Used for styling. - -2. Pass the required props when using the component: - - `showBusinessIcon`: A boolean that, when `true`, displays the business icon. - - `showFamilyIcon`: A boolean that, when `true`, displays the family icon. - - `theme`: Specifies the theme for styling the icons. - -## Accessibility (WCAG) Compliance - -The `CipherInfoIndicatorIcons` component ensures accessibility and usability through its design: - -### Screen Reader Compatibility - -- Icons are rendered as `` elements, and parent components should provide appropriate labeling - or descriptions to convey their meaning to screen readers. - -### Visual Feedback - -- Icons are styled dynamically based on the `theme` to ensure visual clarity and contrast in all - supported themes. -- The size of the icons is fixed at `12px` in height to maintain a consistent visual appearance. - -## Usage Example - -Here's an example of how to integrate the `CipherInfoIndicatorIcons` component: - -```ts -import { CipherInfoIndicatorIcons } from "./cipher-info-indicator-icons"; -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -; -``` - -This example displays the business organization icon, styled for the dark theme, and omits the -family organization icon. - -### Styling Details - -- The component includes the following styles: - -- Icons: Rendered as SVGs with a height of 12px and a width that adjusts to maintain their aspect - ratio. -- Color: Icons are dynamically styled based on the theme, using muted text colors for a subtle - appearance. - -### Notes - -- If neither showBusinessIcon nor showFamilyIcon is set to true, the component renders nothing. This - behavior should be handled by the parent component. diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts deleted file mode 100644 index 99b7e9e0acb..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Meta, StoryObj } from "@storybook/web-components"; - -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -import { NotificationTypes } from "../../../../notification/abstractions/notification-bar"; -import { CipherAction, CipherActionProps } from "../../cipher/cipher-action"; -import { mockI18n } from "../mock-data"; - -export default { - title: "Components/Ciphers/Cipher Action", - argTypes: { - theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - notificationType: { - control: "select", - options: [NotificationTypes.Change, NotificationTypes.Add], - }, - handleAction: { control: false }, - }, - args: { - theme: ThemeTypes.Light, - notificationType: NotificationTypes.Change, - handleAction: () => alert("Action triggered!"), - i18n: mockI18n, - }, -} as Meta; - -const Template = (args: CipherActionProps) => CipherAction({ ...args }); - -export const Default: StoryObj = { - render: Template, -}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts deleted file mode 100644 index d0396b013c8..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Meta, StoryObj } from "@storybook/web-components"; -import { html } from "lit"; - -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -import { CipherIcon, CipherIconProps } from "../../cipher/cipher-icon"; - -export default { - title: "Components/Ciphers/Cipher Icon", - argTypes: { - color: { control: "color" }, - size: { control: "text" }, - theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - uri: { control: "text" }, - }, - args: { - size: "50px", - theme: ThemeTypes.Light, - uri: "", - }, -} as Meta; - -const Template = (args: CipherIconProps) => { - return html` -
- ${CipherIcon({ ...args })} -
- `; -}; - -export const Default: StoryObj = { - render: Template, -}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icons.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icons.lit-stories.ts deleted file mode 100644 index 7b10aeee8de..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icons.lit-stories.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Meta, StoryObj } from "@storybook/web-components"; -import { html } from "lit"; - -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -import { - CipherInfoIndicatorIcons, - CipherInfoIndicatorIconsProps, -} from "../../cipher/cipher-indicator-icons"; -import { OrganizationCategories } from "../../cipher/types"; - -export default { - title: "Components/Ciphers/Cipher Indicator Icons", - argTypes: { - theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - }, - args: { - theme: ThemeTypes.Light, - organizationCategories: [...Object.values(OrganizationCategories)], - }, -} as Meta; - -const Template: StoryObj["render"] = (args) => - html`
${CipherInfoIndicatorIcons({ ...args })}
`; - -export const Default: StoryObj = { - render: Template, -}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-info.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-info.lit-stories.ts deleted file mode 100644 index b56c3193bb8..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-info.lit-stories.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Meta, StoryObj } from "@storybook/web-components"; - -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -import { CipherInfo, CipherInfoProps } from "../../cipher/cipher-info"; -import { mockCiphers } from "../mock-data"; - -export default { - title: "Components/Ciphers/Cipher Info", - argTypes: { - theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - }, - args: { - cipher: mockCiphers[0], - theme: ThemeTypes.Light, - }, -} as Meta; - -const Template = (args: CipherInfoProps) => CipherInfo({ ...args }); - -export const Default: StoryObj = { - render: Template, -}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts index 83b498df7cb..343f36d5e11 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts @@ -4,6 +4,7 @@ import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; import { themes } from "../../constants/styles"; import { ButtonRow, ButtonRowProps } from "../../rows/button-row"; +import { mockBrowserI18nGetMessage } from "../mock-data"; export default { title: "Components/Rows/Button Row", @@ -15,6 +16,49 @@ export default { window.alert("Button clicked!"); }, }, + selectButtons: [ + { + id: "select-1", + label: "select 1", + options: [ + { + text: "item 1", + value: 1, + }, + { + default: true, + text: "item 2", + value: 2, + }, + { + text: "item 3", + value: 3, + }, + ], + }, + { + id: "select-2", + label: "select 2", + options: [ + { + text: "item a", + value: "a", + }, + { + text: "item b", + value: "b", + }, + { + text: "item c", + value: "c", + }, + { + text: "item d", + value: "d", + }, + ], + }, + ], }, } as Meta; @@ -51,3 +95,10 @@ export const Dark: StoryObj = { }, }, }; + +window.chrome = { + ...window.chrome, + i18n: { + getMessage: mockBrowserI18nGetMessage, + }, +} as typeof chrome; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-item.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/rows/cipher-item-row.lit-stories.ts similarity index 62% rename from apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-item.lit-stories.ts rename to apps/browser/src/autofill/content/components/lit-stories/rows/cipher-item-row.lit-stories.ts index 67915db0a7b..59c38c56745 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-item.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/rows/cipher-item-row.lit-stories.ts @@ -3,30 +3,30 @@ import { Meta, StoryObj } from "@storybook/web-components"; import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; import { NotificationTypes } from "../../../../notification/abstractions/notification-bar"; -import { CipherItem, CipherItemProps } from "../../cipher/cipher-item"; +import { CipherItemRow, CipherItemRowProps } from "../../rows/cipher-item-row"; import { mockCiphers, mockI18n } from "../mock-data"; export default { - title: "Components/Ciphers/Cipher Item", + title: "Components/Rows/Cipher Item Row", argTypes: { theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - handleAction: { control: false }, notificationType: { control: "select", - options: [NotificationTypes.Change, NotificationTypes.Add], + options: [...Object.values(NotificationTypes)], }, + handleAction: { control: false }, }, args: { cipher: mockCiphers[0], - theme: ThemeTypes.Light, - notificationType: NotificationTypes.Change, - handleAction: () => alert("Clicked"), i18n: mockI18n, + notificationType: NotificationTypes.Change, + theme: ThemeTypes.Light, + handleAction: () => window.alert("clicked!"), }, -} as Meta; +} as Meta; -const Template = (args: CipherItemProps) => CipherItem({ ...args }); +const Template = (props: CipherItemRowProps) => CipherItemRow({ ...props }); -export const Default: StoryObj = { +export const Default: StoryObj = { render: Template, }; diff --git a/apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts deleted file mode 100644 index 375b793d016..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Meta, StoryObj } from "@storybook/web-components"; - -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -import { ItemRow, ItemRowProps } from "../../rows/item-row"; - -export default { - title: "Components/Rows/Item Row", - argTypes: { - theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - children: { control: "object" }, - }, - args: { - theme: ThemeTypes.Light, - }, -} as Meta; - -const Template = (args: ItemRowProps) => ItemRow({ ...args }); - -export const Default: StoryObj = { - render: Template, -}; diff --git a/apps/browser/src/autofill/content/components/notification/body.ts b/apps/browser/src/autofill/content/components/notification/body.ts index 4d8019b0a55..b1ce7cdba63 100644 --- a/apps/browser/src/autofill/content/components/notification/body.ts +++ b/apps/browser/src/autofill/content/components/notification/body.ts @@ -4,11 +4,10 @@ import { html } from "lit"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; import { NotificationType } from "../../../notification/abstractions/notification-bar"; -import { CipherItem } from "../cipher"; import { NotificationCipherData } from "../cipher/types"; import { I18n } from "../common-types"; import { scrollbarStyles, spacing, themes, typography } from "../constants/styles"; -import { ItemRow } from "../rows/item-row"; +import { CipherItemRow } from "../rows/cipher-item-row"; export const componentClassPrefix = "notification-body"; @@ -37,15 +36,12 @@ export function NotificationBody({ return html`
${ciphers.map((cipher) => - ItemRow({ + CipherItemRow({ + cipher, theme, - children: CipherItem({ - cipher, - i18n, - notificationType, - theme, - handleAction: handleEditOrUpdateAction, - }), + i18n, + notificationType, + handleAction: handleEditOrUpdateAction, }), )}
diff --git a/apps/browser/src/autofill/content/components/rows/cipher-item-row.ts b/apps/browser/src/autofill/content/components/rows/cipher-item-row.ts new file mode 100644 index 00000000000..0600fc9ac4b --- /dev/null +++ b/apps/browser/src/autofill/content/components/rows/cipher-item-row.ts @@ -0,0 +1,78 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { NotificationType } from "../../../notification/abstractions/notification-bar"; +import { CipherItem } from "../cipher/cipher-item"; +import { NotificationCipherData } from "../cipher/types"; +import { I18n } from "../common-types"; +import { spacing, themes, typography } from "../constants/styles"; + +export type CipherItemRowProps = { + cipher: NotificationCipherData; + i18n: I18n; + notificationType?: NotificationType; + theme: Theme; + handleAction: (e: Event) => void; +}; + +export function CipherItemRow({ + cipher, + i18n, + notificationType, + theme, + handleAction, +}: CipherItemRowProps) { + return html` +
+ ${CipherItem({ + cipher, + i18n, + notificationType, + theme, + handleAction, + })} +
+ `; +} + +const cipherItemRowStyles = ({ theme }: { theme: Theme }) => css` + ${typography.body1} + + gap: ${spacing["2"]}; + display: flex; + align-items: center; + justify-content: space-between; + border-width: 0 0 0.5px 0; + border-style: solid; + border-radius: ${spacing["2"]}; + border-color: ${themes[theme].secondary["300"]}; + background-color: ${themes[theme].background.DEFAULT}; + padding: ${spacing["2"]} ${spacing["3"]}; + min-height: min-content; + max-height: 52px; + overflow-x: hidden; + white-space: nowrap; + color: ${themes[theme].text.main}; + font-weight: 400; + + > div { + :first-child { + flex: 3 3 75%; + min-width: 25%; + } + + :not(:first-child) { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-end; + max-width: 25%; + + > button { + max-width: min-content; + } + } + } +`; diff --git a/apps/browser/src/autofill/content/components/rows/item-row.ts b/apps/browser/src/autofill/content/components/rows/item-row.ts deleted file mode 100644 index 8e9a870002e..00000000000 --- a/apps/browser/src/autofill/content/components/rows/item-row.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { css } from "@emotion/css"; -import { html, TemplateResult } from "lit"; - -import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; - -import { spacing, themes, typography } from "../../../content/components/constants/styles"; - -export type ItemRowProps = { - theme: Theme; - children: TemplateResult | TemplateResult[]; -}; - -export function ItemRow({ theme = ThemeTypes.Light, children }: ItemRowProps) { - return html`
${children}
`; -} - -export const itemRowStyles = ({ theme }: { theme: Theme }) => css` - ${typography.body1} - - gap: ${spacing["2"]}; - display: flex; - align-items: center; - justify-content: space-between; - border-width: 0 0 0.5px 0; - border-style: solid; - border-radius: ${spacing["2"]}; - border-color: ${themes[theme].secondary["300"]}; - background-color: ${themes[theme].background.DEFAULT}; - padding: ${spacing["2"]} ${spacing["3"]}; - min-height: min-content; - max-height: 52px; - overflow-x: hidden; - white-space: nowrap; - color: ${themes[theme].text.main}; - font-weight: 400; - - > div { - :first-child { - flex: 3 3 75%; - min-width: 25%; - } - - :not(:first-child) { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: flex-end; - max-width: 25%; - - > button { - max-width: min-content; - } - } - } -`; From 57911f210bcebb68557c31633ae8c9ff2c277731 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 22 May 2025 10:20:33 -0700 Subject: [PATCH 06/41] [PM-21896] - prevent double reprompt for copy password in desktop cipher form (#14883) * prevent double reprompt for copy password in desktop cipher form * adjust name * fix input name --- apps/desktop/src/vault/app/vault/vault.component.html | 1 + apps/desktop/src/vault/app/vault/view.component.ts | 4 ++++ libs/angular/src/vault/components/view.component.ts | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/vault/app/vault/vault.component.html b/apps/desktop/src/vault/app/vault/vault.component.html index 99131a848cc..9a25619b1a8 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.html +++ b/apps/desktop/src/vault/app/vault/vault.component.html @@ -15,6 +15,7 @@ *ngIf="cipherId && action === 'view'" [cipherId]="cipherId" [collectionId]="activeFilter?.selectedCollectionId" + [masterPasswordAlreadyPrompted]="cipherRepromptId === cipherId" (onCloneCipher)="cloneCipherWithoutPasswordPrompt($event)" (onEditCipher)="editCipher($event)" (onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)" diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 084a9a747ed..68bf8d6d1a3 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -3,6 +3,7 @@ import { ChangeDetectorRef, Component, EventEmitter, + Input, NgZone, OnChanges, OnDestroy, @@ -46,6 +47,7 @@ const BroadcasterSubscriptionId = "ViewComponent"; }) export class ViewComponent extends BaseViewComponent implements OnInit, OnDestroy, OnChanges { @Output() onViewCipherPasswordHistory = new EventEmitter(); + @Input() masterPasswordAlreadyPrompted: boolean = false; constructor( cipherService: CipherService, @@ -120,6 +122,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro } }); }); + this.passwordReprompted = this.masterPasswordAlreadyPrompted; } ngOnDestroy() { @@ -134,6 +137,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro }); return; } + this.passwordReprompted = this.masterPasswordAlreadyPrompted; } viewHistory() { diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 8915cb6b671..fd3f92c6c73 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -95,7 +95,7 @@ export class ViewComponent implements OnDestroy, OnInit { cipherType = CipherType; private previousCipherId: string; - private passwordReprompted = false; + protected passwordReprompted = false; /** * Represents TOTP information including display formatting and timing From bd29397fd8859a6bf48b40f769682d835979823e Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Thu, 22 May 2025 13:55:26 -0500 Subject: [PATCH 07/41] [PM-21611] Require userId on KeyService clear methods (#14788) --- .../settings/account-security.component.ts | 3 +- .../service-container/service-container.ts | 2 +- .../src/app/accounts/settings.component.ts | 3 +- apps/web/src/app/app.component.ts | 2 +- .../vault-timeout-settings.service.ts | 2 +- .../vault-timeout-settings.service.ts | 2 +- .../src/abstractions/key.service.ts | 6 ++- libs/key-management/src/key.service.spec.ts | 44 +++++++++++-------- libs/key-management/src/key.service.ts | 12 ++--- 9 files changed, 41 insertions(+), 35 deletions(-) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index d7b8aa573d4..26a805b3624 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -480,7 +480,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { }); await this.vaultNudgesService.dismissNudge(NudgeType.AccountSecurity, userId); } else { - await this.vaultTimeoutSettingsService.clear(); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.vaultTimeoutSettingsService.clear(userId); } } diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index cdf6c4bbfda..bc5db09da26 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -874,7 +874,7 @@ export class ServiceContainer { const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await Promise.all([ this.eventUploadService.uploadEvents(userId as UserId), - this.keyService.clearKeys(), + this.keyService.clearKeys(userId), this.cipherService.clear(userId), this.folderService.clear(userId), this.collectionService.clear(userId), diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 83c982fbaba..fd0585e805e 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -506,7 +506,8 @@ export class SettingsComponent implements OnInit, OnDestroy { await this.updateRequirePasswordOnStart(); } - await this.vaultTimeoutSettingsService.clear(); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.vaultTimeoutSettingsService.clear(userId); } this.messagingService.send("redrawMenu"); diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index cac0487d05d..3de9bf0a8c8 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -282,7 +282,7 @@ export class AppComponent implements OnDestroy, OnInit { ); await Promise.all([ - this.keyService.clearKeys(), + this.keyService.clearKeys(userId), this.cipherService.clear(userId), this.folderService.clear(userId), this.collectionService.clear(userId), diff --git a/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts b/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts index 7094a2c2f83..9ff362e4009 100644 --- a/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts +++ b/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts @@ -59,5 +59,5 @@ export abstract class VaultTimeoutSettingsService { */ isBiometricLockSet: (userId?: string) => Promise; - clear: (userId?: string) => Promise; + clear: (userId: UserId) => Promise; } diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts index 0716bf0bb93..07b8a8c297d 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts @@ -287,7 +287,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA return availableActions; } - async clear(userId?: string): Promise { + async clear(userId: UserId): Promise { await this.keyService.clearPinKeys(userId); } diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index d67fec4c98e..95b79890c6a 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -370,8 +370,9 @@ export abstract class KeyService { * Note: This will remove the stored pin and as a result, * disable pin protection for the user * @param userId The desired user + * @throws Error when provided userId is null or undefined */ - abstract clearPinKeys(userId?: string): Promise; + abstract clearPinKeys(userId: UserId): Promise; /** * @param keyMaterial The key material to derive the send key from * @returns A new send key @@ -380,8 +381,9 @@ export abstract class KeyService { /** * Clears all of the user's keys from storage * @param userId The user's Id + * @throws Error when provided userId is null or undefined */ - abstract clearKeys(userId?: string): Promise; + abstract clearKeys(userId: UserId): Promise; abstract randomNumber(min: number, max: number): Promise; /** * Generates a new cipher key diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 25d8aff99fb..f1f1286dfc5 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -1,5 +1,5 @@ import { mock } from "jest-mock-extended"; -import { BehaviorSubject, bufferCount, firstValueFrom, lastValueFrom, of, take, tap } from "rxjs"; +import { BehaviorSubject, bufferCount, firstValueFrom, lastValueFrom, of, take } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; @@ -380,16 +380,12 @@ describe("keyService", () => { }); describe("clearKeys", () => { - it("resolves active user id when called with no user id", async () => { - let callCount = 0; - stateProvider.activeUserId$ = stateProvider.activeUserId$.pipe(tap(() => callCount++)); - - await keyService.clearKeys(); - expect(callCount).toBe(1); - - // revert to the original state - accountService.activeAccount$ = accountService.activeAccountSubject.asObservable(); - }); + test.each([null as unknown as UserId, undefined as unknown as UserId])( + "throws when the provided userId is %s", + async (userId) => { + await expect(keyService.clearKeys(userId)).rejects.toThrow("UserId is required"); + }, + ); describe.each([ USER_ENCRYPTED_ORGANIZATION_KEYS, @@ -397,14 +393,6 @@ describe("keyService", () => { USER_ENCRYPTED_PRIVATE_KEY, USER_KEY, ])("key removal", (key: UserKeyDefinition) => { - it(`clears ${key.key} for active user when unspecified`, async () => { - await keyService.clearKeys(); - - const encryptedOrgKeyState = stateProvider.singleUser.getFake(mockUserId, key); - expect(encryptedOrgKeyState.nextMock).toHaveBeenCalledTimes(1); - expect(encryptedOrgKeyState.nextMock).toHaveBeenCalledWith(null); - }); - it(`clears ${key.key} for the specified user when specified`, async () => { const userId = "someOtherUser" as UserId; await keyService.clearKeys(userId); @@ -416,6 +404,24 @@ describe("keyService", () => { }); }); + describe("clearPinKeys", () => { + test.each([null as unknown as UserId, undefined as unknown as UserId])( + "throws when the provided userId is %s", + async (userId) => { + await expect(keyService.clearPinKeys(userId)).rejects.toThrow("UserId is required"); + }, + ); + it("calls pin service to clear", async () => { + const userId = "someOtherUser" as UserId; + + await keyService.clearPinKeys(userId); + + expect(pinService.clearPinKeyEncryptedUserKeyPersistent).toHaveBeenCalledWith(userId); + expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).toHaveBeenCalledWith(userId); + expect(pinService.clearUserKeyEncryptedPin).toHaveBeenCalledWith(userId); + }); + }); + describe("userPrivateKey$", () => { type SetupKeysParams = { makeMasterKey: boolean; diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 849b9db6a50..9372dafd3ea 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -564,11 +564,9 @@ export class DefaultKeyService implements KeyServiceAbstraction { await this.stateProvider.setUserState(USER_ENCRYPTED_PRIVATE_KEY, null, userId); } - async clearPinKeys(userId?: UserId): Promise { - userId ??= await firstValueFrom(this.stateProvider.activeUserId$); - + async clearPinKeys(userId: UserId): Promise { if (userId == null) { - throw new Error("Cannot clear PIN keys, no user Id resolved."); + throw new Error("UserId is required"); } await this.pinService.clearPinKeyEncryptedUserKeyPersistent(userId); @@ -588,11 +586,9 @@ export class DefaultKeyService implements KeyServiceAbstraction { return (await this.keyGenerationService.createKey(512)) as CipherKey; } - async clearKeys(userId?: UserId): Promise { - userId ??= await firstValueFrom(this.stateProvider.activeUserId$); - + async clearKeys(userId: UserId): Promise { if (userId == null) { - throw new Error("Cannot clear keys, no user Id resolved."); + throw new Error("UserId is required"); } await this.masterPasswordService.clearMasterKeyHash(userId); From 5702a17772f44b60ea6637efd4c34a6ced12ac82 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 11:57:58 +0200 Subject: [PATCH 08/41] Autosync the updated translations (#14892) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 50 +- apps/browser/src/_locales/az/messages.json | 50 +- apps/browser/src/_locales/be/messages.json | 50 +- apps/browser/src/_locales/bg/messages.json | 50 +- apps/browser/src/_locales/bn/messages.json | 50 +- apps/browser/src/_locales/bs/messages.json | 50 +- apps/browser/src/_locales/ca/messages.json | 52 +- apps/browser/src/_locales/cs/messages.json | 50 +- apps/browser/src/_locales/cy/messages.json | 50 +- apps/browser/src/_locales/da/messages.json | 50 +- apps/browser/src/_locales/de/messages.json | 50 +- apps/browser/src/_locales/el/messages.json | 50 +- apps/browser/src/_locales/en_GB/messages.json | 50 +- apps/browser/src/_locales/en_IN/messages.json | 50 +- apps/browser/src/_locales/es/messages.json | 76 +- apps/browser/src/_locales/et/messages.json | 50 +- apps/browser/src/_locales/eu/messages.json | 50 +- apps/browser/src/_locales/fa/messages.json | 1310 +++++++++-------- apps/browser/src/_locales/fi/messages.json | 92 +- apps/browser/src/_locales/fil/messages.json | 50 +- apps/browser/src/_locales/fr/messages.json | 50 +- apps/browser/src/_locales/gl/messages.json | 50 +- apps/browser/src/_locales/he/messages.json | 50 +- apps/browser/src/_locales/hi/messages.json | 50 +- apps/browser/src/_locales/hr/messages.json | 50 +- apps/browser/src/_locales/hu/messages.json | 50 +- apps/browser/src/_locales/id/messages.json | 50 +- apps/browser/src/_locales/it/messages.json | 50 +- apps/browser/src/_locales/ja/messages.json | 50 +- apps/browser/src/_locales/ka/messages.json | 50 +- apps/browser/src/_locales/km/messages.json | 50 +- apps/browser/src/_locales/kn/messages.json | 50 +- apps/browser/src/_locales/ko/messages.json | 50 +- apps/browser/src/_locales/lt/messages.json | 92 +- apps/browser/src/_locales/lv/messages.json | 50 +- apps/browser/src/_locales/ml/messages.json | 50 +- apps/browser/src/_locales/mr/messages.json | 50 +- apps/browser/src/_locales/my/messages.json | 50 +- apps/browser/src/_locales/nb/messages.json | 50 +- apps/browser/src/_locales/ne/messages.json | 50 +- apps/browser/src/_locales/nl/messages.json | 50 +- apps/browser/src/_locales/nn/messages.json | 50 +- apps/browser/src/_locales/or/messages.json | 50 +- apps/browser/src/_locales/pl/messages.json | 50 +- apps/browser/src/_locales/pt_BR/messages.json | 50 +- apps/browser/src/_locales/pt_PT/messages.json | 50 +- apps/browser/src/_locales/ro/messages.json | 50 +- apps/browser/src/_locales/ru/messages.json | 78 +- apps/browser/src/_locales/si/messages.json | 50 +- apps/browser/src/_locales/sk/messages.json | 52 +- apps/browser/src/_locales/sl/messages.json | 50 +- apps/browser/src/_locales/sr/messages.json | 102 +- apps/browser/src/_locales/sv/messages.json | 50 +- apps/browser/src/_locales/te/messages.json | 50 +- apps/browser/src/_locales/th/messages.json | 50 +- apps/browser/src/_locales/tr/messages.json | 50 +- apps/browser/src/_locales/uk/messages.json | 50 +- apps/browser/src/_locales/vi/messages.json | 50 +- apps/browser/src/_locales/zh_CN/messages.json | 64 +- apps/browser/src/_locales/zh_TW/messages.json | 50 +- 60 files changed, 3254 insertions(+), 1214 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index c96f4a5803b..48ae14fc1ce 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "لم يتم العثور على معرف فريد." }, - "convertOrganizationEncryptionDesc": { - "message": "يستخدم $ORGANIZATION$ SSO مع خادم مفتاح الاستضافة الذاتية. لم تعد هناك حاجة إلى كلمة مرور رئيسية لتسجيل الدخول لأعضاء هذه المنظمة.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "مغادرة المؤسسة" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 51eb1345466..c9096bce0d6 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Unikal identifikator tapılmadı." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$, self-hosted açar serveri ilə SSO istifadə edir. Bu təşkilatın üzvlərinin giriş etməsi üçün artıq ana parol tələb edilməyəcək.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Aşağıdakı təşkilatların üzvləri üçün artıq ana parol tələb olunmur. Lütfən aşağıdakı domeni təşkilatınızın inzibatçısı ilə təsdiqləyin." + }, + "organizationName": { + "message": "Təşkilat adı" + }, + "keyConnectorDomain": { + "message": "Key Connector domeni" }, "leaveOrganization": { "message": "Təşkilatı tərk et" @@ -3615,6 +3615,14 @@ "message": "Şifrələnmiş məlumatları hər kəslə güvənli şəkildə paylaşmaq üçün \"Send\"i istifadə edin.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send, həssas məlumatlar təhlükəsizdir", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "İstənilən platformada faylları və dataları hər kəslə paylaşın. İfşa olunmağı məhdudlaşdıraraq məlumatlarınız ucdan-uca şifrələnmiş qalacaq.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Giriş lazımdır." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." }, + "unlockVault": { + "message": "Seyfinizin kilidini saniyələr ərzində açın" + }, + "unlockVaultDesc": { + "message": "Seyfinizə daha cəld müraciət etmək üçün kilid açma və bitmə vaxtı ayarlarını özəlləşdirə bilərsiniz." + }, + "unlockPinSet": { + "message": "PIN ilə kilid açma təyini" + }, "authenticating": { "message": "Kimlik doğrulama" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Cəld parol yaradın" + }, + "generatorNudgeBodyOne": { + "message": "Klikləyərək güclü və unikal parolları asanlıqla yaradın", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "və girişlərinizi güvənli şəkildə saxlayın.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Girişlərinizi güvənli şəkildə saxlamağınıza kömək etməsi üçün Parol yarat düyməsinə klikləyərək güclü və unikal parolları asanlıqla yaradın.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Bu səhifəyə baxmaq icazəniz yoxdur. Fərqli hesabla giriş etməyə çalışın." } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index a51b96547da..0dd5ff4163f 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Не знойдзены ўнікальны ідэнтыфікатар." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ выкарыстоўвае SSO з уласным серверам ключоў. Асноўны пароль для ўдзельнікаў гэтай арганізацыі больш не патрабуецца.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Выйсці з арганізацыі" @@ -3615,6 +3615,14 @@ "message": "Выкарыстоўвайце Send'ы, каб бяспечна абагуляць зашыфраваную інфармацыю з іншымі.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Неабходны ўвод даных." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 09e5260f9b3..0bf8dbce2b0 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Няма намерен уникален идентификатор." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ използва еднократно удостоверяване със собствен сървър за ключове. Членовете на тази организация вече нямат нужда от главна парола за вписване.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "За членовете на следната организация вече не се изисква главна парола. Потвърдете домейна по-долу с администратора на организацията си." + }, + "organizationName": { + "message": "Име на организацията" + }, + "keyConnectorDomain": { + "message": "Домейн на конектора за ключове" }, "leaveOrganization": { "message": "Напускане на организацията" @@ -3615,6 +3615,14 @@ "message": "Използвайте Изпращане, за да споделите безопасно шифрована информация с някого.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Изпращайте чувствителна информация сигурно", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Споделяйте сигурно файлове и данни с всекиго, през всяка система. Информацията Ви ще бъде защитена с шифроване от край до край, а видимостта ѝ ще бъде ограничена.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Полето е задължително да бъде попълнено." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Отключването с биометрични данни не е налично по неизвестна причина." }, + "unlockVault": { + "message": "Отключвайте трезора си за секунди" + }, + "unlockVaultDesc": { + "message": "Можете да персонализирате настройките си за отключване и време на активност, за да получавате достъп до трезора си по-бързо." + }, + "unlockPinSet": { + "message": "Зададен е ПИН код за отключване" + }, "authenticating": { "message": "Удостоверяване" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Създавайте пароли бързо" + }, + "generatorNudgeBodyOne": { + "message": "Създавайте лесно сложни и уникални пароли като щракнете върху", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "за да защитите данните си за вписване.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Създавайте лесно сложни и уникални пароли като щракнете върху бутона за генериране на парола, за да защитите данните си за вписване.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Нямате права за преглед на тази страница. Опитайте да се впишете с друг акаунт." } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 5e19936e975..f7f28115faa 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index f48037814e5..1dc35addd8e 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 151412c02ed..9517257ac3a 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1057,7 +1057,7 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "notificationAddDesc": { - "message": "Bitwarden ha de recordar aquesta contrasenya per a vosaltres?" + "message": "Ha de recordar Bitwarden aquesta contrasenya per a vosaltres?" }, "notificationAddSave": { "message": "Guarda" @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No s'ha trobat cap identificador únic." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ està utilitzant SSO amb un servidor autoallotjat de claus. Ja no es requereix una contrasenya mestra d'inici de sessió per als membres d'aquesta organització.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandona l'organització" @@ -3615,6 +3615,14 @@ "message": "Utilitzeu Send per compartir informació xifrada de manera segura amb qualsevol persona.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "L'entrada és obligatòria." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "S'està autenticant" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index effc4c950c6..b213e0aee7d 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nenalezen žádný jedinečný identifikátor." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používá SSO s vlastním serverem s klíči. Hlavní heslo pro členy této organizace již není vyžadováno.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavní heslo již není vyžadováno pro členy následující organizace. Potvrďte níže uvedenou doménu u správce Vaší organizace." + }, + "organizationName": { + "message": "Název organizace" + }, + "keyConnectorDomain": { + "message": "Doména Key Connectoru" }, "leaveOrganization": { "message": "Opustit organizaci" @@ -3615,6 +3615,14 @@ "message": "Použijte Send pro bezpečné sdílení šifrovaných informací s kýmkoliv.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Posílejte citlivé informace bezpečně", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Sdílejte bezpečně soubory a data s kýmkoli na libovolné platformě. Vaše informace zůstanou šifrovány a zároveň omezují expozici.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Je vyžadován vstup." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." }, + "unlockVault": { + "message": "Rychlé odemknutí trezoru" + }, + "unlockVaultDesc": { + "message": "Pro rychlejší přístup k trezoru můžete upravit nastavení odemknutí a vypršení časového limitu." + }, + "unlockPinSet": { + "message": "PIN pro odemknutí byl nastaven" + }, "authenticating": { "message": "Ověřování" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Rychlé vytvoření hesla" + }, + "generatorNudgeBodyOne": { + "message": "Jednoduše vytvořte silná a unikátní hesla klepnutím na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "aby Vám pomohlo udržet Vaše přihlašovací údaje v bezpečí.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Jednoduše vytvořte silná a unikátní hesla klepnutím na tlačítko Generovat heslo, které Vám pomůže udržet Vaše přihlašovací údaje v bezpečí.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Nemáte oprávnění k zobrazení této stránky. Zkuste se přihlásit jiným účtem." } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 047b45dd9b8..ee74d1e45e2 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index e6233b1f8db..da24e1bcfb9 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen entydig identifikator fundet." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruger SSO med en selv-hostet nøgleserver. En hovedadgangskode er ikke længere påkrævet for at logge ind for medlemmer af denne organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlad organisation" @@ -3615,6 +3615,14 @@ "message": "Brug Send til at dele krypterede oplysninger sikkert med nogen.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input obligatorisk." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Godkender" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 1eac22c919c..f7303885551 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Keine eindeutige Kennung gefunden." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ verwendet SSO mit einem selbst gehosteten Schlüsselserver. Ein Master-Passwort ist nicht mehr erforderlich, damit sich Mitglieder dieser Organisation anmelden können.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Für Mitglieder der folgenden Organisation ist kein Master-Passwort mehr erforderlich. Bitte bestätige die folgende Domain bei deinem Organisations-Administrator." + }, + "organizationName": { + "message": "Name der Organisation" + }, + "keyConnectorDomain": { + "message": "Key Connector-Domain" }, "leaveOrganization": { "message": "Organisation verlassen" @@ -3615,6 +3615,14 @@ "message": "Verwende Send, um verschlüsselte Informationen sicher mit anderen zu teilen.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Sensible Informationen sicher versenden", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Teile Dateien und Daten sicher mit jedem auf jeder Plattform. Deine Informationen bleiben Ende-zu-Ende-Verschlüsselt, während die Verbreitung begrenzt wird.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Eingabe ist erforderlich." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." }, + "unlockVault": { + "message": "Entsperre deinen Tresor in Sekunden" + }, + "unlockVaultDesc": { + "message": "Du kannst deine Entsperr- und Timeout-Einstellungen anpassen, um schneller auf deinen Tresor zuzugreifen." + }, + "unlockPinSet": { + "message": "Entsperr-PIN festgelegt" + }, "authenticating": { "message": "Authentifizierung" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Passwörter schnell erstellen" + }, + "generatorNudgeBodyOne": { + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", um deine Zugangsdaten sicher aufzubewahren.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den \"Passwort generieren\"-Button klickst, um deine Zugangsdaten sicher aufzubewahren.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Du hast keine Berechtigung, diese Seite anzuzeigen. Versuche dich mit einem anderen Konto anzumelden." } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index faad1a90a07..ed81dfa744e 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Δε βρέθηκε μοναδικό αναγνωριστικό." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ χρησιμοποιεί SSO με έναν αυτοεξυπηρετητή κλειδιών. Ένας κύριος κωδικός πρόσβασης δεν απαιτείται πλέον για να συνδεθείτε για τα μέλη αυτού του οργανισμού.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Αποχώρηση από τον οργανισμό" @@ -3615,6 +3615,14 @@ "message": "Χρήση Send για ασφαλή κοινοποίηση κρυπτογραφημένων πληροφοριών με οποιονδήποτε.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Απαιτείται εισαγωγή." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Ταυτοποίηση" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index d131d3ec33d..5eecf989894 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "organizationName": { + "message": "Organisation name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organisation" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customise your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index aa1c076b2e9..adb3cf6ec32 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "organizationName": { + "message": "Organisation name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave Organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customise your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 3018cd36ed4..94d02848b5f 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "Logo de Bitwarden" }, "extName": { "message": "Bitwarden - Administrador de contraseñas", @@ -656,7 +656,7 @@ "message": "Tu navegador web no soporta copiar al portapapeles facilmente. Cópialo manualmente." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "Verifica tu identidad" }, "weDontRecognizeThisDevice": { "message": "No reconocemos este dispositivo. Introduce el código enviado a tu correo electrónico para verificar tu identidad." @@ -872,16 +872,16 @@ "message": "Iniciar sesión en Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Introduce el código enviado a tu correo electrónico" }, "enterTheCodeFromYourAuthenticatorApp": { "message": "Introduce el código de tu aplicación de autenticación" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "Pulsa tu YubiKey para identificarte" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "Se requiere inicio de sesión en dos pasos para tu cuenta. Sigue los pasos siguientes para terminar de iniciar sesión." }, "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." @@ -911,7 +911,7 @@ "message": "No" }, "location": { - "message": "Location" + "message": "Ubicación" }, "unexpectedError": { "message": "Ha ocurrido un error inesperado." @@ -1063,7 +1063,7 @@ "message": "Guardar" }, "notificationViewAria": { - "message": "View $ITEMNAME$, opens in new window", + "message": "Ver $ITEMNAME$, se abre en una nueva ventana", "placeholders": { "itemName": { "content": "$1" @@ -1072,7 +1072,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "Nuevo Elemento, se abre en una nueva ventana", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -2594,7 +2594,7 @@ "message": "Illustration of the Bitwarden autofill menu displaying a generated password." }, "updateInBitwarden": { - "message": "Update in Bitwarden" + "message": "Actualizar en Bitwarden" }, "updateInBitwardenSlideDesc": { "message": "Bitwarden will then prompt you to update the password in the password manager.", @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Identificador único no encontrado." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO con un servidor de claves autoalojado. Los miembros de esta organización ya no necesitarán una contraseña maestra para iniciar sesión.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Ya no es necesaria una contraseña maestra para los miembros de la siguiente organización. Confirma el dominio que aparece a continuación con el administrador de tu organización." + }, + "organizationName": { + "message": "Nombre de la organización" + }, + "keyConnectorDomain": { + "message": "Dominio del conector de clave" }, "leaveOrganization": { "message": "Abandonar organización" @@ -3406,7 +3406,7 @@ "message": "Inicio de sesión en proceso" }, "logInRequestSent": { - "message": "Request sent" + "message": "Solicitud enviada" }, "exposedMasterPassword": { "message": "Contraseña maestra comprometida" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Entrada requerida." }, @@ -3817,7 +3825,7 @@ "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Nueva tarjeta", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { @@ -4553,10 +4561,10 @@ "message": "Download from bitwarden.com now" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "Consíguela en Google Play" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "Descarga en la App Store" }, "permanentlyDeleteAttachmentConfirmation": { "message": "¿Estás seguro de que deseas eliminar permanentemente este adjunto?" @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticando" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 5bce2142219..26b4bf69fb4 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Unikaalset identifikaatorit ei leitud." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kasutab SSO-d koos enda majutatud võtmeserveriga. Selle organisatsiooni liikmed ei pea sisselogimisel enam ülemparooli kasutama.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lahku organisatsioonist" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Sisestus on nõutav." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 54b7b9b234c..b3f525d7be5 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ez da identifikatzaile bakarrik aurkitu." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ SSO erabiltzen ari da ostatatze propioa duen gako-zerbitzari batekin. Dagoeneko ez da pasahitz nagusirik behar erakunde honetako kideentzat saioa hasteko.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Utzi erakundea" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index f132b61fc4e..33a779a7909 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -6,7 +6,7 @@ "message": "لوگو Bitwarden" }, "extName": { - "message": "مدیریت رمز عبور Bitwarden", + "message": "مدیریت کلمه عبور Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { @@ -35,10 +35,10 @@ "message": "خوش آمدید" }, "setAStrongPassword": { - "message": "تنظیم رمز عبور قوی" + "message": "تنظیم کلمه عبور قوی" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "ایجاد حساب خود را با تنظیم رمز عبور تکمیل کنید" + "message": "ایجاد حساب کاربری خود را با تنظیم کلمه عبور تکمیل کنید" }, "enterpriseSingleSignOn": { "message": "ورود به سیستم پروژه" @@ -303,7 +303,7 @@ "message": "درباره استفاده از Bitwarden در مرکز راهنما بیشتر بیاموزید." }, "continueToBrowserExtensionStore": { - "message": "آیا میخواهید به فروشگاه افزونه مرورگر ادامه دهید?" + "message": "آیا می‌خواهید به فروشگاه افزونه مرورگر ادامه دهید؟" }, "continueToBrowserExtensionStoreDesc": { "message": "به دیگران کمک کنید تا بفهمند آیا Bitwarden برایشان مناسب است یا نه. به فروشگاه افزونه مرورگر خود بروید و نظر خود را به اشتراک بگذارید." @@ -341,7 +341,7 @@ "message": "Bitwarden برای کسب و کارها" }, "bitwardenAuthenticator": { - "message": "تاییدکننده هویت Bitwarden" + "message": "تأییدکننده هویت Bitwarden" }, "continueToAuthenticatorPageDesc": { "message": "احراز هویت کننده Bitwarden به شما امکان می‌دهد کلیدهای احراز هویت را ذخیره کرده و کدهای TOTP را برای فرآیندهای تأیید دومرحله‌ای تولید کنید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید" @@ -796,7 +796,7 @@ "message": "حساب کاربری جدید شما ساخته شد! حالا می‌توانید وارد شوید." }, "newAccountCreated2": { - "message": "حساب جدید شما ایجاد شده است!" + "message": "حساب کاربری جدید شما ایجاد شده است!" }, "youHaveBeenLoggedIn": { "message": "شما با موفقیت وارد شدید!" @@ -839,55 +839,55 @@ "message": "کلید احراز هویت اضافه شد" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "اسکن کد QR احراز هویت کننده از صفحه وب فعلی" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "تأیید دو مرحله‌ای را بدون دردسر کنید" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. کلید را کپی کرده و در این فیلد قرار دهید." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. برای اسکن کد QR احراز هویت کننده این وب‌سایت، روی آیکون دوربین کلیک کنید یا کلید را کپی کرده و در این فیلد قرار دهید." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "درباره احراز هویت کننده‌ها بیشتر بدانید" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "کپی کلید احراز هویت (TOTP)" }, "loggedOut": { "message": "خارج شد" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "شما از حساب خود خارج شده‌اید." }, "loginExpired": { "message": "نشست ورود شما منقضی شده است." }, "logIn": { - "message": "Log in" + "message": "ورود" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "وارد Bitwarden شوید" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "کد سامانه تأیید کننده را وارد نمایید" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "برای احراز هویت، کلید YubiKey خود را فشار دهید" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "برای حساب کاربری شما ورود دو مرحله‌ای Duo لازم است. مراحل زیر را دنبال کنید تا ورود خود را کامل کنید." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "مراحل زیر را دنبال کنید تا وارد سیستم شوید." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Follow the steps below to finish logging in with your security key." + "message": "مراحل زیر را برای کامل کردن ورود با کلید امنیتی خود دنبال کنید." }, "restartRegistration": { "message": "ثبت‌نام را دوباره آغاز کنید" @@ -896,10 +896,10 @@ "message": "پیوند منقضی شد" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "لطفاً ثبت نام را مجدداً شروع کنید یا دوباره وارد شوید." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "ممکن است قبلاً حساب کاربری داشته باشید" }, "logOutConfirmation": { "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" @@ -926,10 +926,10 @@ "message": "ورود دو مرحله ای باعث می‌شود که حساب کاربری شما با استفاده از یک دستگاه دیگر مانند کلید امنیتی، برنامه احراز هویت، پیامک، تماس تلفنی و یا ایمیل، اعتبار خود را با ایمنی بیشتر اثبات کند. ورود دو مرحله ای می تواند در bitwarden.com فعال شود. آیا می‌خواهید از سایت بازدید کنید؟" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "با راه‌اندازی ورود دو مرحله‌ای در برنامه وب Bitwarden، حساب کاربری خود را ایمن‌تر کنید." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "با برنامه وب ادامه می‌دهید؟" }, "editedFolder": { "message": "پوشه ذخیره شد" @@ -1016,16 +1016,16 @@ "message": "درخواست افزودن ورود به سیستم" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "گزینه‌های ذخیره در گاوصندوق" }, "addLoginNotificationDesc": { "message": "در صورتی که موردی در گاوصندوق شما یافت نشد، درخواست افزودن کنید." }, "addLoginNotificationDescAlt": { - "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." + "message": "اگر موردی در گاوصندوق شما یافت نشد، درخواست افزودن آن را بدهید. این مورد برای همه حساب‌های وارد شده اعمال می‌شود." }, "showCardsInVaultViewV2": { - "message": "Always show cards as Autofill suggestions on Vault view" + "message": "همیشه کارت‌ها را به‌عنوان پیشنهادهای پر کردن خودکار در نمای گاوصندوق نمایش بده" }, "showCardsCurrentTab": { "message": "نمایش کارت‌ها در صفحه برگه" @@ -1034,7 +1034,7 @@ "message": "برای پر کردن خودکار آسان، موارد کارت را در صفحه برگه فهرست کن." }, "showIdentitiesInVaultViewV2": { - "message": "Always show identities as Autofill suggestions on Vault view" + "message": "همیشه هویت‌ها را به‌عنوان پیشنهادهای پر کردن خودکار در نمای گاوصندوق نمایش بده" }, "showIdentitiesCurrentTab": { "message": "نشان دادن هویت در صفحه برگه" @@ -1043,10 +1043,10 @@ "message": "موارد هویتی را در صفحه برگه برای پر کردن خودکار آسان فهرست کن." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "برای پر کردن خودکار، روی موردها در نمای گاوصندوق کلیک کنید" }, "clickToAutofill": { - "message": "Click items in autofill suggestion to fill" + "message": "برای پر کردن، روی موردها در پیشنهادهای پرکردن خودکار کلیک کنید" }, "clearClipboard": { "message": "پاکسازی کلیپ بورد", @@ -1063,7 +1063,7 @@ "message": "ذخیره" }, "notificationViewAria": { - "message": "View $ITEMNAME$, opens in new window", + "message": "مشاهده $ITEMNAME$، در پنجره جدید باز می‌شود", "placeholders": { "itemName": { "content": "$1" @@ -1072,18 +1072,18 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "مورد جدید، در پنجره جدید باز می‌شود", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { - "message": "Edit before saving", + "message": "ویرایش قبل ذخیره کردن", "description": "Tooltip and Aria label for edit button on cipher item" }, "newNotification": { - "message": "New notification" + "message": "اعلان جدید" }, "labelWithNotification": { - "message": "$LABEL$: New notification", + "message": "$LABEL$: اعلان جدید", "description": "Label for the notification with a new login suggestion.", "placeholders": { "label": { @@ -1093,15 +1093,15 @@ } }, "notificationLoginSaveConfirmation": { - "message": "saved to Bitwarden.", + "message": "در Bitwarden ذخیره شد.", "description": "Shown to user after item is saved." }, "notificationLoginUpdatedConfirmation": { - "message": "updated in Bitwarden.", + "message": "در Bitwarden به‌روزرسانی شد.", "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "انتخاب $ITEMTYPE$، $ITEMNAME$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -1113,35 +1113,35 @@ } }, "saveAsNewLoginAction": { - "message": "Save as new login", + "message": "ذخیره به عنوان ورود جدید", "description": "Button text for saving login details as a new entry." }, "updateLoginAction": { - "message": "Update login", + "message": "به‌روزرسانی ورود", "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "برای ذخیره این ورود، قفل را باز کنید", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { - "message": "Save login", + "message": "ذخیره ورود", "description": "Prompt asking the user if they want to save their login details." }, "updateLogin": { - "message": "Update existing login", + "message": "به‌روزرسانی ورود به سیستم موجود", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { - "message": "Login saved", + "message": "ورود ذخیره شد", "description": "Message displayed when login details are successfully saved." }, "loginUpdateSuccess": { - "message": "Login updated", + "message": "ورود به‌روزرسانی شد", "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { - "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", + "message": "آفرین! شما اقداماتی را انجام دادید تا خودتان و $ORGANIZATION$ را ایمن‌تر کنید.", "placeholders": { "organization": { "content": "$1" @@ -1150,7 +1150,7 @@ "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { - "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", + "message": "ممنون که $ORGANIZATION$ را ایمن‌تر کردید. شما $TASK_COUNT$ کلمات عبور دیگر برای به‌روزرسانی دارید.", "placeholders": { "organization": { "content": "$1" @@ -1162,15 +1162,15 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "تغییر کلمه عبور بعدی", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { - "message": "Error saving", + "message": "خطای ذخیره", "description": "Error message shown when the system fails to save login details." }, "saveFailureDetails": { - "message": "Oh no! We couldn't save this. Try entering the details manually.", + "message": "اوه نه! نتوانستیم این را ذخیره کنیم. لطفاً جزئیات را به‌صورت دستی وارد کنید.", "description": "Detailed error message shown when saving login details fails." }, "enableChangedPasswordNotification": { @@ -1180,13 +1180,13 @@ "message": "هنگامی که تغییری در یک وب‌سایت شناسایی شد، درخواست به‌روزرسانی کلمه عبور ورود کن." }, "changedPasswordNotificationDescAlt": { - "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." + "message": "هنگامی که تغییری در کلمه عبور یک ورود در وب‌سایت شناسایی شود، درخواست به‌روزرسانی آن را بده. این مورد برای همه حساب‌های وارد شده اعمال می‌شود." }, "enableUsePasskeys": { "message": "برای ذخیره و استفاده از passkey اجازه بگیر" }, "usePasskeysDesc": { - "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." + "message": "درخواست ذخیره کلیدهای عبور جدید یا ورود با کلیدهای عبوری که در گاوصندوق شما ذخیره شده‌اند. این مورد برای همه حساب‌های وارد شده اعمال می‌شود." }, "notificationChangeDesc": { "message": "آیا مایل به به‌روزرسانی این کلمه عبور در Bitwarden هستید؟" @@ -1210,7 +1210,7 @@ "message": "از یک کلیک ثانویه برای دسترسی به تولید کلمه عبور و ورودهای منطبق برای وب سایت استفاده کن." }, "contextMenuItemDescAlt": { - "message": "Use a secondary click to access password generation and matching logins for the website. Applies to all logged in accounts." + "message": "برای دسترسی به تولید کلمه عبور و ورودهای منطبق با وب‌سایت، از کلیک ثانویه استفاده کنید. این مورد برای همه حساب‌های کاربری وارد شده اعمال می‌شود." }, "defaultUriMatchDetection": { "message": "بررسی مطابقت نشانی اینترنتی پیش‌فرض", @@ -1226,7 +1226,7 @@ "message": "تغییر رنگ پوسته برنامه." }, "themeDescAlt": { - "message": "Change the application's color theme. Applies to all logged in accounts." + "message": "تغییر تم رنگی برنامه. این مورد برای همه حساب‌های کاربری وارد شده اعمال می‌شود." }, "dark": { "message": "تاریک", @@ -1237,7 +1237,7 @@ "description": "Light color" }, "exportFrom": { - "message": "صادرات از" + "message": "برون ریزی از" }, "exportVault": { "message": "برون ریزی گاوصندوق" @@ -1246,35 +1246,35 @@ "message": "فرمت پرونده" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "این پرونده برون ریزی با کلمه عبور محافظت می‌شود و برای رمزگشایی به کلمه عبور پرونده نیاز دارد." }, "filePassword": { - "message": "رمز فایل" + "message": "کلمه عبور پرونده" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "این کلمه عبور برای برون ریزی و درون ریزی این پرونده استفاده می‌شود" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "برای رمزگذاری برون ریزی و محدود کردن درون ریزی فقط به حساب کاربری فعلی Bitwarden، از کلید رمزگذاری حساب خود که از نام کاربری و کلمه عبور اصلی حساب شما مشتق شده است استفاده کنید." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "یک کلمه عبور برای پرونده به‌منظور رمزگذاری تنظیم کنید تا برون ریزی و درون ریزی آن به هر حساب Bitwarden با استفاده از کلمه عبور رمزگشایی شود." }, "exportTypeHeading": { - "message": "نوع صادرات" + "message": "نوع برون ریزی" }, "accountRestricted": { "message": "حساب کاربری محدود شده است" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "عدم تطابق \"رمز فایل\" و \"تایید رمز فایل\" با یکدیگر." + "message": "عدم تطابق \"کلمه عبور پرونده\" و \"تأیید کلمه عبور پرونده\" با یکدیگر." }, "warning": { "message": "اخطار", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "هشدار", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { @@ -1296,7 +1296,7 @@ "message": "اشتراک گذاری شد" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." + "message": "Bitwarden for Business به شما اجازه می‌دهد تا با استفاده از یک سازمان، موارد گاوصندوق خود را با دیگران به اشتراک بگذارید. در وب سایت bitwarden.com بیشتر بیاموزید." }, "moveToOrganization": { "message": "انتقال به سازمان" @@ -1354,7 +1354,7 @@ "message": "پرونده" }, "fileToShare": { - "message": "File to share" + "message": "پرونده‌ای برای اشتراک‌گذاری" }, "selectFile": { "message": "ﺍﻧﺘﺨﺎﺏ یک ﭘﺮﻭﻧﺪﻩ" @@ -1390,13 +1390,13 @@ "message": "۱ گیگابایت فضای ذخیره سازی رمزگذاری شده برای پیوست های پرونده." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "دسترسی اضطراری." }, "premiumSignUpTwoStepOptions": { "message": "گزینه های ورود اضافی دو مرحله ای مانند YubiKey و Duo." }, "ppremiumSignUpReports": { - "message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." + "message": "گزارش‌های بهداشت کلمه عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." }, "ppremiumSignUpTotp": { "message": "تولید کننده کد تأیید (2FA) از نوع TOTP برای ورودهای در گاوصندوقتان." @@ -1411,7 +1411,7 @@ "message": "خرید پرمیوم" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "می‌توانید نسخه پرمیوم را از تنظیمات حساب کاربری خود در اپلیکیشن وب Bitwarden خریداری کنید." }, "premiumCurrentMember": { "message": "شما یک عضو پرمیوم هستید!" @@ -1420,7 +1420,7 @@ "message": "برای حمایتتان از Bitwarden سپاسگزاریم." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "ارتقا به نسخه پرمیوم و دریافت:" }, "premiumPrice": { "message": "تمامش فقط $PRICE$ در سال!", @@ -1432,7 +1432,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "تمامش فقط $PRICE$ در سال!", "placeholders": { "price": { "content": "$1", @@ -1459,10 +1459,10 @@ "message": "برای استفاده از این ویژگی عضویت پرمیوم لازم است." }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "پایان زمان احراز هویت" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "نشست احراز هویت منقضی شد. لطفاً فرایند ورود را دوباره شروع کنید." }, "verificationCodeEmailSent": { "message": "ایمیل تأیید به $EMAIL$ ارسال شد.", @@ -1474,29 +1474,29 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "در این دستگاه به مدت ۳۰ روز دوباره نپرس" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "انتخاب روش دیگر", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "از کد بازیابی‌تان استفاده کنید" }, "insertU2f": { "message": "کلید امنیتی خود را وارد پورت USB رایانه کنید، اگر دکمه ای دارد آن را بفشارید." }, "openInNewTab": { - "message": "Open in new tab" + "message": "گشودن در زبانهٔ جدید" }, "webAuthnAuthenticate": { "message": "تأیید اعتبار در WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "خواندن کلید امنیتی" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "در انتظار تعامل با کلید امنیتی..." }, "loginUnavailable": { "message": "ورود به سیستم در دسترس نیست" @@ -1511,7 +1511,7 @@ "message": "گزینه‌های ورود دو مرحله‌ای" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "انتخاب ورود دو مرحله‌ای" }, "recoveryCodeDesc": { "message": "دسترسی به تمامی ارائه‌دهندگان دو مرحله‌ای را از دست داده‌اید؟ از کد بازیابی خود برای غیرفعال‌سازی ارائه‌دهندگان دو مرحله‌ای از حسابتان استفاده کنید." @@ -1523,17 +1523,17 @@ "message": "برنامه احراز هویت" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "کدی را وارد کنید که توسط یک برنامه احراز هویت مانند Bitwarden Authenticator تولید شده است.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "کلید امنیت Yubico OTP" }, "yubiKeyDesc": { "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. همراه با دستگاه‌های YubiKey 4 ،4 Nano ،NEO کار می‌کند." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "کدی را وارد کنید که توسط Duo Security تولید شده است.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1550,19 +1550,19 @@ "message": "ایمیل" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید." }, "selfHostedEnvironment": { "message": "محیط خود میزبان" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "نشانی اینترنتی پایه نصب Bitwarden خود را که به‌صورت داخلی میزبانی شده مشخص کنید.\nمثال: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "برای پیکربندی پیشرفته، می‌توانید نشانی اینترنتی پایه هر سرویس را به‌صورت مستقل مشخص کنید." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, "customEnvironment": { "message": "محیط سفارشی" @@ -1571,7 +1571,7 @@ "message": "نشانی اینترنتی سرور" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "نشانی اینترنتی سرور خود میزبان", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1593,20 +1593,20 @@ "message": "نشانی‌های اینترنتی محیط ذخیره شد" }, "showAutoFillMenuOnFormFields": { - "message": "Show autofill menu on form fields", + "message": "نمایش منوی پر کردن خودکار روی فیلدهای فرم", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "پیشنهادهای پر کردن خودکار" }, "autofillSpotlightTitle": { - "message": "Easily find autofill suggestions" + "message": "یافتن آسان پیشنهادهای پر کردن خودکار" }, "autofillSpotlightDesc": { - "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + "message": "تنظیمات پر کردن خودکار مرورگر خود را غیرفعال کنید تا با Bitwarden تداخل نداشته باشد." }, "turnOffBrowserAutofill": { - "message": "Turn off $BROWSER$ autofill", + "message": "پر کردن خودکار $BROWSER$ را غیرفعال کنید", "placeholders": { "browser": { "content": "$1", @@ -1615,25 +1615,25 @@ } }, "turnOffAutofill": { - "message": "Turn off autofill" + "message": "پر کردن خودکار را خاموش کنید" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "نمایش پیشنهادهای پر کردن خودکار روی فیلدهای فرم" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "نمایش هویت‌ها به‌عنوان پیشنهاد" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "نمایش کارت‌ها به‌عنوان پیشنهاد" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "نمایش پیشنهادها هنگام انتخاب آیکون" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "برای تمام حساب‌های کاربری وارد شده اعمال می‌شود." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Turn off your browser's built in password manager settings to avoid conflicts." + "message": "تنظیمات مدیر کلمه عبور داخلی مرورگر خود را غیرفعال کنید تا از بروز تداخل جلوگیری شود." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "ویرایش تنظیمات مرورگر." @@ -1643,15 +1643,15 @@ "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "وقتی فیلد انتخاب شد (در حالت فوکوس)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "When autofill icon is selected", + "message": "وقتی آیکون پر کردن خودکار انتخاب شود", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "پر کردن خودکار هنگام بارگذاری صفحه" }, "enableAutoFillOnPageLoad": { "message": "پر کردن خودکار هنگام بارگذاری صفحه" @@ -1663,7 +1663,7 @@ "message": "وب‌سایت‌های در معرض خطر یا نامعتبر می‌توانند از پر کردن خودکار در بارگذاری صفحه سوء استفاده کنند." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "درباره‌ خطراتش بیش‌تر بدانید" }, "learnMoreAboutAutofill": { "message": "درباره پر کردن خودکار بیشتر بدانید" @@ -1693,13 +1693,13 @@ "message": "باز کردن گاوصندوق در نوار کناری" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "آخرین ورودی مورد استفاده برای وب سایت فعلی را به صورت خودکار پر کنید" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "آخرین کارت مورد استفاده برای وب سایت فعلی را به صورت خودکار پر کنید" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "آخرین هویت مورد استفاده برای وب سایت فعلی را به صورت خودکار پر کنید" }, "commandGeneratePasswordDesc": { "message": "یک کلمه عبور تصادفی جدید ایجاد کنید و آن را در کلیپ بورد کپی کنید" @@ -1723,7 +1723,7 @@ "message": "برای مرتب‌سازی بکشید" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "برای تغییر ترتیب، بکشید و رها کنید" }, "cfTypeText": { "message": "متن" @@ -1735,7 +1735,7 @@ "message": "منطقی" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "کادر انتخاب" }, "cfTypeLinked": { "message": "پیوند شده", @@ -1758,7 +1758,7 @@ "message": "یک تصویر قابل تشخیص در کنار هر ورود نشان دهید." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "نمایش تصویر قابل تشخیص کنار هر ورود به سیستم. برای تمام حساب‌های کاربری وارد شده اعمال می‌شود." }, "enableBadgeCounter": { "message": "نمایش شمارنده نشان" @@ -1920,7 +1920,7 @@ "message": "هویت" }, "typeSshKey": { - "message": "SSH key" + "message": "کلید SSH" }, "newItemHeader": { "message": "$TYPE$ جدید", @@ -1941,7 +1941,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "مشاهده $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1953,13 +1953,13 @@ "message": "تاریخچه کلمه عبور" }, "generatorHistory": { - "message": "Generator history" + "message": "تاریخچه تولید کننده" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "پاک کردن تاریخچه تولید کننده" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "اگر ادامه دهید، تمام ورودی‌ها به‌طور دائمی از تاریخچه تولید کننده حذف خواهند شد. آیا مطمئن هستید که می‌خواهید ادامه دهید؟" }, "back": { "message": "بازگشت" @@ -1968,7 +1968,7 @@ "message": "مجموعه‌ها" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ مجموعه‌ها", "placeholders": { "count": { "content": "$1", @@ -1998,7 +1998,7 @@ "message": "یادداشت‌های امن" }, "sshKeys": { - "message": "SSH Keys" + "message": "کلید‌‌های SSH" }, "clear": { "message": "پاک کردن", @@ -2024,7 +2024,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "دامنه پایه (پیشنهادی)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -2078,13 +2078,13 @@ "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, "clearHistory": { - "message": "Clear history" + "message": "پاک کردن تاریخچه" }, "nothingToShow": { - "message": "Nothing to show" + "message": "چیزی برای نشان دادن موجود نیست" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "شما اخیراً چیزی تولید نکرده‌اید" }, "remove": { "message": "حذف" @@ -2145,16 +2145,16 @@ "message": "باز کردن با پین" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "تنظیم کد پین" }, "setYourPinButton": { - "message": "Set PIN" + "message": "تنظیم کد پین" }, "setYourPinCode": { "message": "کد پین خود را برای باز کردن Bitwarden تنظیم کنید. اگر به طور کامل از برنامه خارج شوید، تنظیمات پین شما از بین می‌رود." }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "کد پین شما برای باز کردن Bitwarden به جای کلمه عبور اصلی استفاده خواهد شد. در صورتی که کاملاً از Bitwarden خارج شوید، کد پین شما ریست خواهد شد." }, "pinRequired": { "message": "کد پین الزامیست." @@ -2163,13 +2163,13 @@ "message": "کد پین معتبر نیست." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "تعداد تلاش‌های ناموفق پین زیاد شد. خارج می‌شوید." }, "unlockWithBiometrics": { "message": "با استفاده از بیومتریک باز کنید" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "باز کردن قفل با کلمه عبور اصلی" }, "awaitDesktop": { "message": "در انتظار تأیید از دسکتاپ" @@ -2181,7 +2181,7 @@ "message": "در زمان شروع مجدد، با کلمه عبور اصلی قفل کن" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "در زمان شروع مجدد، کلمه عبور اصلی نیاز است" }, "selectOneCollection": { "message": "شما باید حداقل یک مجموعه را انتخاب کنید." @@ -2193,39 +2193,39 @@ "message": "شبیه سازی" }, "passwordGenerator": { - "message": "Password generator" + "message": "تولید کننده کلمه عبور" }, "usernameGenerator": { - "message": "Username generator" + "message": "تولید کننده نام کاربری" }, "useThisEmail": { - "message": "Use this email" + "message": "از این ایمیل استفاده شود" }, "useThisPassword": { - "message": "Use this password" + "message": "از این کلمه عبور استفاده کن" }, "useThisUsername": { - "message": "Use this username" + "message": "از این نام کاربری استفاده کن" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "کلمه عبور ایمن ساخته شد! فراموش نکنید کلمه عبور خود را در وب‌سایت نیز به‌روزرسانی کنید." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "از تولید کننده استفاده کنید", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "برای ایجاد یک کلمه عبور قوی و منحصر به فرد", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { - "message": "Vault customization" + "message": "سفارشی‌سازی گاوصندوق" }, "vaultTimeoutAction": { "message": "عمل متوقف شدن گاو‌صندوق" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "اقدام در صورت پایان زمان" }, "lock": { "message": "قفل", @@ -2254,7 +2254,7 @@ "message": "مورد بازیابی شد" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "در حال حاضر حساب کاربری دارید؟" }, "vaultTimeoutLogOutConfirmation": { "message": "خروج از سیستم، تمام دسترسی ها به گاو‌صندوق شما را از بین می‌برد و نیاز به احراز هویت آنلاین پس از مدت زمان توقف دارد. آیا مطمئن هستید که می‌خواهید از این تنظیمات استفاده کنید؟" @@ -2347,7 +2347,7 @@ "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پیروی نمی‌کند." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "پیشنهادها، اعلان‌ها و فرصت‌های تحقیقاتی Bitwarden را در صندوق ورودی خود دریافت کنید." }, "unsubscribe": { "message": "لغو اشتراک" @@ -2374,7 +2374,7 @@ "message": "سیاست حفظ حریم خصوصی" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "کلمه عبور جدید شما نمی‌تواند با کلمه عبور فعلی‌تان یکسان باشد." }, "hintEqualsPassword": { "message": "اشاره به کلمه عبور شما نمی‌تواند همان کلمه عبور شما باشد." @@ -2383,10 +2383,10 @@ "message": "تأیید" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "خطای به‌روزرسانی توکن دسترسی" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "هیچ توکن به‌روزرسانی یا کلید API یافت نشد. لطفاً از حساب کاربری خود خارج شده و دوباره وارد شوید." }, "desktopSyncVerificationTitle": { "message": "تأیید همگام‌سازی دسکتاپ" @@ -2425,10 +2425,10 @@ "message": "عدم مطابقت حساب کاربری" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "عدم تطابق کلید بیومتریک" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + "message": "باز کردن قفل بیومتریک ناموفق بود. کلید مخفی بیومتریک نتوانست گاوصندوق را باز کند. لطفاً دوباره تنظیمات بیومتریک را انجام دهید." }, "biometricsNotEnabledTitle": { "message": "بیومتریک برپا نشده" @@ -2446,13 +2446,13 @@ "message": "کاربر قفل یا خارج شد" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "لطفاً این کاربر را در برنامه دسکتاپ باز کنید و دوباره تلاش کنید." }, "biometricsNotAvailableTitle": { - "message": "Biometric unlock unavailable" + "message": "باز کردن قفل بیومتریک در دسترس نیست" }, "biometricsNotAvailableDesc": { - "message": "Biometric unlock is currently unavailable. Please try again later." + "message": "باز کردن قفل بیومتریک در حال حاضر در دسترس نیست. لطفاً بعداً دوباره تلاش کنید." }, "biometricsFailedTitle": { "message": "زیست‌سنجی ناموفق بود" @@ -2479,17 +2479,17 @@ "message": "سیاست سازمانی بر تنظیمات مالکیت شما تأثیر می‌گذارد." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "یک سیاست سازمانی، درون ریزی موارد به گاوصندوق فردی شما را مسدود کرده است." }, "domainsTitle": { - "message": "Domains", + "message": "دامنه‌ها", "description": "A category title describing the concept of web domains" }, "blockedDomains": { - "message": "Blocked domains" + "message": "دامنه‌های مسدود شده" }, "learnMoreAboutBlockedDomains": { - "message": "Learn more about blocked domains" + "message": "اطلاعات بیشتر درباره دامنه‌های مسدود شده" }, "excludedDomains": { "message": "دامنه های مستثنی" @@ -2498,22 +2498,22 @@ "message": "Bitwarden برای ذخیره جزئیات ورود به سیستم این دامنه ها سوال نمی‌کند. برای اینکه تغییرات اعمال شود باید صفحه را تازه کنید." }, "excludedDomainsDescAlt": { - "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." + "message": "Bitwarden برای هیچ یک از حساب‌های کاربری وارد شده، درخواست ذخیره اطلاعات ورود برای این دامنه‌ها را نخواهد داد. برای اعمال تغییرات باید صفحه را تازه‌سازی کنید." }, "blockedDomainsDesc": { - "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + "message": "ویژگی‌های پر کردن خودکار و سایر قابلیت‌های مرتبط برای این وب‌سایت‌ها ارائه نخواهند شد. برای اعمال تغییرات باید صفحه را تازه‌سازی کنید." }, "autofillBlockedNoticeV2": { - "message": "Autofill is blocked for this website." + "message": "پر کردن خودکار برای این وب‌سایت مسدود شده است." }, "autofillBlockedNoticeGuidance": { - "message": "Change this in settings" + "message": "این مورد را در تنظیمات تغییر دهید" }, "change": { - "message": "Change" + "message": "تغییر" }, "changeButtonTitle": { - "message": "Change password - $ITEMNAME$", + "message": "تغییر کلمه عبور - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -2522,10 +2522,10 @@ } }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "کلمات عبور در معرض خطر" }, "atRiskPasswordDescSingleOrg": { - "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "message": "$ORGANIZATION$ از شما درخواست کرده یک کلمه عبور را به دلیل در معرض خطر بودن تغییر دهید.", "placeholders": { "organization": { "content": "$1", @@ -2534,7 +2534,7 @@ } }, "atRiskPasswordsDescSingleOrgPlural": { - "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "message": "$ORGANIZATION$ از شما درخواست کرده $COUNT$ کلمه عبور را به دلیل در معرض خطر بودن تغییر دهید.", "placeholders": { "organization": { "content": "$1", @@ -2547,7 +2547,7 @@ } }, "atRiskPasswordsDescMultiOrgPlural": { - "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "message": "سازمان‌های شما از شما درخواست کرده‌اند که $COUNT$ کلمه عبور را به دلیل در معرض خطر بودن تغییر دهید.", "placeholders": { "count": { "content": "$1", @@ -2556,10 +2556,10 @@ } }, "reviewAndChangeAtRiskPassword": { - "message": "Review and change one at-risk password" + "message": "بررسی و تغییر یک کلمه عبور در معرض خطر" }, "reviewAndChangeAtRiskPasswordsPlural": { - "message": "Review and change $COUNT$ at-risk passwords", + "message": "بررسی و تغییر $COUNT$ کلمه عبور در معرض خطر", "placeholders": { "count": { "content": "$1", @@ -2568,52 +2568,52 @@ } }, "changeAtRiskPasswordsFaster": { - "message": "Change at-risk passwords faster" + "message": "تغییر سریع‌تر کلمات عبور در معرض خطر" }, "changeAtRiskPasswordsFasterDesc": { - "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + "message": "تنظیمات خود را به‌روزرسانی کنید تا بتوانید به‌سرعت کلمات عبور خود را به‌صورت خودکار وارد کرده و کلمات جدید تولید کنید" }, "reviewAtRiskLogins": { - "message": "Review at-risk logins" + "message": "بررسی ورودهای در معرض خطر" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords" + "message": "بررسی کلمات عبور در معرض خطر" }, "reviewAtRiskLoginsSlideDesc": { - "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "message": "کلمات عبور سازمان شما در معرض خطر هستند زیرا ضعیف، تکراری و یا افشا شده‌اند.", "description": "Description of the review at-risk login slide on the at-risk password page carousel" }, "reviewAtRiskLoginSlideImgAltPeriod": { - "message": "Illustration of a list of logins that are at-risk." + "message": "تصویری از فهرست ورودهایی که در معرض خطر هستند." }, "generatePasswordSlideDesc": { - "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "message": "با استفاده از منوی پر کردن خودکار Bitwarden در سایت در معرض خطر، به‌سرعت یک کلمه عبور قوی و منحصر به فرد تولید کنید.", "description": "Description of the generate password slide on the at-risk password page carousel" }, "generatePasswordSlideImgAltPeriod": { - "message": "Illustration of the Bitwarden autofill menu displaying a generated password." + "message": "تصویری از منوی پر کردن خودکار Bitwarden که یک کلمه عبور تولید شده را نمایش می‌دهد." }, "updateInBitwarden": { - "message": "Update in Bitwarden" + "message": "به‌روزرسانی در Bitwarden" }, "updateInBitwardenSlideDesc": { - "message": "Bitwarden will then prompt you to update the password in the password manager.", + "message": "سپس Bitwarden از شما می‌خواهد کلمه عبور را در مدیریت کلمه عبور به‌روزرسانی کنید.", "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" }, "updateInBitwardenSlideImgAltPeriod": { - "message": "Illustration of a Bitwarden’s notification prompting the user to update the login." + "message": "تصویری از اعلان Bitwarden که از کاربر می‌خواهد ورود خود را به‌روزرسانی کند." }, "turnOnAutofill": { - "message": "Turn on autofill" + "message": "پر کردن خودکار را فعال کنید" }, "turnedOnAutofill": { - "message": "Turned on autofill" + "message": "پر کردن خودکار فعال شد" }, "dismiss": { - "message": "Dismiss" + "message": "نادیده گرفتن" }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "وب‌سایت $number$ (نشانی اینترنتی)", "placeholders": { "number": { "content": "$1", @@ -2631,20 +2631,20 @@ } }, "blockedDomainsSavedSuccess": { - "message": "Blocked domain changes saved" + "message": "تغییرات دامنه مسدود شده ذخیره شد" }, "excludedDomainsSavedSuccess": { - "message": "Excluded domain changes saved" + "message": "تغییرات دامنه مستثنی شده ذخیره شد" }, "limitSendViews": { - "message": "Limit views" + "message": "محدود کردن نمایش‌ها" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "هیچ‌کس نمی‌تواند این را پس از رسیدن به محدودیت مشاهده کند.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "$ACCESSCOUNT$ بازدید باقی مانده", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2658,14 +2658,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", + "message": "جزئیات ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { "message": "متن" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "متن برای اشتراک گذاری" }, "sendTypeFile": { "message": "پرونده" @@ -2675,7 +2675,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" + "message": "متن را به‌صورت پیش‌فرض مخفی کن" }, "expired": { "message": "منقضی شده" @@ -2684,7 +2684,7 @@ "message": "محافظت ‌شده با کلمه عبور" }, "copyLink": { - "message": "Copy link" + "message": "کپی پیوند" }, "copySendLink": { "message": "پیوند ارسال را کپی کن", @@ -2722,7 +2722,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { - "message": "Are you sure you want to permanently delete this Send?", + "message": "مطمئن هستید می‌خواهید این ارسال را برای همیشه پاک کنید؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -2733,7 +2733,7 @@ "message": "تاریخ حذف" }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "ارسال در این تاریخ به‌طور دائمی حذف خواهد شد.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -2755,7 +2755,7 @@ "message": "سفارشی" }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", + "message": "یک کلمه عبور اختیاری برای دریافت‌کنندگان اضافه کنید تا بتوانند به این ارسال دسترسی داشته باشند.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { @@ -2778,15 +2778,15 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "ارسال با موفقیت ساخته شد!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { - "message": "The Send will be available to anyone with the link for the next 1 hour.", + "message": "این ارسال به مدت ۱ ساعت برای هر کسی که لینک را دارد در دسترس خواهد بود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHours": { - "message": "The Send will be available to anyone with the link for the next $HOURS$ hours.", + "message": "این ارسال به مدت $HOURS$ ساعت برای هر کسی که لینک را دارد در دسترس خواهد بود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2796,11 +2796,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "این ارسال به مدت ۱ روز برای هر کسی که لینک را دارد در دسترس خواهد بود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInDays": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "این ارسال به مدت $DAYS$ روز برای هر کسی که لینک را دارد در دسترس خواهد بود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2810,7 +2810,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "لینک ارسال کپی شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -2818,11 +2818,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogText": { - "message": "Pop out extension?", + "message": "باز کردن پنجره جداگانه افزونه؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { - "message": "To create a file Send, you need to pop out the extension to a new window.", + "message": "برای ایجاد یک ارسال پرونده، باید افزونه را در پنجره‌ای جدید باز کنید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { @@ -2835,7 +2835,7 @@ "message": "برای انتخاب پرونده ای با استفاده از Safari، با کلیک روی این بنر پنجره جدیدی باز کنید." }, "popOut": { - "message": "Pop out" + "message": "باز کردن در پنجره جداگانه" }, "sendFileCalloutHeader": { "message": "قبل از اینکه شروع کنی" @@ -2856,7 +2856,7 @@ "message": "هنگام ذخیره حذف و تاریخ انقضاء شما خطایی روی داد." }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "آدرس ایمیل خود را از بینندگان مخفی کنید." }, "passwordPrompt": { "message": "درخواست مجدد کلمه عبور اصلی" @@ -2871,7 +2871,7 @@ "message": "تأیید ایمیل لازم است" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "ایمیل تأیید شد" }, "emailVerificationRequiredDesc": { "message": "برای استفاده از این ویژگی باید ایمیل خود را تأیید کنید. می‌توانید ایمیل خود را در گاوصندوق وب تأیید کنید." @@ -2889,7 +2889,7 @@ "message": "کلمه عبور اصلی شما با یک یا چند سیاست سازمان‌تان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "سازمان شما رمزگذاری دستگاه‌های مورد اعتماد را غیرفعال کرده است. لطفاً برای دسترسی به گاوصندوق خود یک کلمه عبور اصلی تنظیم کنید." }, "resetPasswordPolicyAutoEnroll": { "message": "ثبت نام خودکار" @@ -2905,15 +2905,15 @@ "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": "مجوزهای سازمان شما به‌روزرسانی شد، باید یک کلمه عبور اصلی تنظیم کنید.", "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": "سازمانتان از شما می‌خواهد که یک کلمه عبور اصلی تنظیم کنید.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "از میان $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2932,7 +2932,7 @@ "message": "دقیقه" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "نیازمندی‌های سیاست سازمانی بر گزینه‌های زمان پایان نشست شما اعمال شده است" }, "vaultTimeoutPolicyInEffect": { "message": "سیاست‌های سازمانتان بر مهلت زمانی گاوصندوق شما تأثیر می‌گذارد. حداکثر زمان مجاز گاوصندوق $HOURS$ ساعت و $MINUTES$ دقیقه است", @@ -2948,7 +2948,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "حداکثر $HOURS$ ساعت و $MINUTES$ دقیقه.", "placeholders": { "hours": { "content": "$1", @@ -2961,7 +2961,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "زمان پایان نشست بیشتر از محدودیتی است که سازمان شما تعیین کرده است: حداکثر $HOURS$ ساعت و $MINUTES$ دقیقه", "placeholders": { "hours": { "content": "$1", @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "شناسه منحصر به فردی یافت نشد." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ در حال استفاده از SSO با یک سرور کلید خود میزبان است. برای ورود اعضای این سازمان دیگر نیازی به کلمه عبور اصلی نیست.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "برای اعضای سازمان زیر، کلمه عبور اصلی دیگر لازم نیست. لطفاً دامنه زیر را با مدیر سازمان خود تأیید کنید." + }, + "organizationName": { + "message": "نام سازمان" + }, + "keyConnectorDomain": { + "message": "دامنه رابط کلید" }, "leaveOrganization": { "message": "ترک سازمان" @@ -3057,7 +3057,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "فقط موردهای فردی گاوصندوق شامل پیوست‌ها که به $EMAIL$ مرتبط هستند برون ریزی خواهند شد. موردهای گاوصندوق سازمانی شامل نمی‌شوند", "placeholders": { "email": { "content": "$1", @@ -3066,10 +3066,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "در حال برون ریزی گاوصندوق سازمان" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "فقط گاوصدوق سازمان مرتبط با $ORGANIZATION$ برون ریزی خواهد شد. موارد موجود در گاوصندوق‌های فردی یا سایر سازمان‌ها شامل نمی‌شوند.", "placeholders": { "organization": { "content": "$1", @@ -3081,27 +3081,27 @@ "message": "خطا" }, "decryptionError": { - "message": "Decryption error" + "message": "خطای رمزگشایی" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden نتوانست مورد(های) گاوصندوق فهرست شده زیر را رمزگشایی کند." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "با بخش پشتیبانی مشتریان تماس بگیرید", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "برای جلوگیری از، از دست دادن داده‌های بیشتر.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "ایجاد نام کاربری" }, "generateEmail": { - "message": "Generate email" + "message": "تولید ایمیل" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "مقدار باید بین $MIN$ و $MAX$ باشد.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -3115,7 +3115,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " برای تولید یک کلمه عبور قوی، از $RECOMMENDED$ کاراکتر یا بیشتر استفاده کنید.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3125,7 +3125,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " برای تولید یک عبارت عبور قوی، از $RECOMMENDED$ واژه یا بیشتر استفاده کنید.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3166,15 +3166,15 @@ "message": "یک نام مستعار ایمیل با یک سرویس ارسال خارجی ایجاد کنید." }, "forwarderDomainName": { - "message": "Email domain", + "message": "دامنه ایمیل", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "دامنه‌ای را انتخاب کنید که توسط سرویس انتخاب شده پشتیبانی می‌شود", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ خطا: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -3188,11 +3188,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "وب‌سایت: $WEBSITE$. تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -3202,7 +3202,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "توکن API نامعتبر برای $SERVICENAME$", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -3212,7 +3212,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "توکن API نامعتبر برای $SERVICENAME$: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -3226,7 +3226,7 @@ } }, "forwaderInvalidOperation": { - "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "message": "$SERVICENAME$ درخواست شما را رد کرد. لطفاً برای دریافت کمک با ارائه‌دهنده سرویس خود تماس بگیرید.", "description": "Displayed when the user is forbidden from using the API by the forwarding service.", "placeholders": { "servicename": { @@ -3236,7 +3236,7 @@ } }, "forwaderInvalidOperationWithMessage": { - "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "message": "$SERVICENAME$ درخواست شما را رد کرد: $ERRORMESSAGE$", "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -3250,7 +3250,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "دریافت شناسه حساب ایمیل ماسک شده از $SERVICENAME$ امکان‌پذیر نیست.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -3290,7 +3290,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "فرواردکننده ناشناخته: $SERVICENAME$.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3379,34 +3379,34 @@ "message": "ارسال مجدد اعلان" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "مشاهده همه گزینه‌های ورود به سیستم" }, "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, "notificationSentDevicePart1": { - "message": "Unlock Bitwarden on your device or on the" + "message": "قفل Bitwarden را در دستگاه خود یا در... باز کنید" }, "notificationSentDeviceAnchor": { - "message": "web app" + "message": "برنامه وب" }, "notificationSentDevicePart2": { - "message": "Make sure the Fingerprint phrase matches the one below before approving." + "message": "اطمینان حاصل کنید که عبارت اثر انگشت با عبارت زیر مطابقت دارد قبل از تأیید." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "یک اعلان به دستگاه شما ارسال شده است" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "به‌محض تأیید درخواست، به شما اطلاع داده خواهد شد" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "به گزینه دیگری نیاز دارید؟" }, "loginInitiated": { "message": "ورود به سیستم آغاز شد" }, "logInRequestSent": { - "message": "Request sent" + "message": "درخواست ارسال شد" }, "exposedMasterPassword": { "message": "کلمه عبور اصلی افشا شده" @@ -3445,7 +3445,7 @@ "message": "نحوه پر کردن خودکار" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "یک مورد را از این صفحه انتخاب کنید، از میان‌بر $COMMAND$ استفاده کنید، یا گزینه‌های دیگر را در تنظیمات بررسی کنید.", "placeholders": { "command": { "content": "$1", @@ -3454,7 +3454,7 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "یک مورد را از این صفحه انتخاب کنید یا گزینه‌های دیگر را در تنظیمات بررسی کنید." }, "gotIt": { "message": "متوجه شدم" @@ -3463,22 +3463,22 @@ "message": "تنظیمات پر کردن خودکار" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "میان‌بر پرکردن خودکار" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "تغییر میان‌بر" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "مدیریت میان‌برها" }, "autofillShortcut": { "message": "میانبر صفحه کلید پر کردن خودکار" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "میان‌بر ورود خودکار تنظیم نشده است. این مورد را در تنظیمات مرورگر تغییر دهید." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "میان‌بر ورود خودکار $COMMAND$ است. همه میان‌برها را می‌توانید در تنظیمات مرورگر مدیریت کنید.", "placeholders": { "command": { "content": "$1", @@ -3499,16 +3499,16 @@ "message": "در پنجره جدید باز می‌شود" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "این دستگاه را به خاطر بسپار تا ورودهای بعدی بدون مشکل انجام شود" }, "deviceApprovalRequired": { "message": "تأیید دستگاه لازم است. یک روش تأیید انتخاب کنید:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "تأیید دستگاه لازم است" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "یکی از گزینه‌های تأیید زیر را انتخاب کنید" }, "rememberThisDevice": { "message": "این دستگاه را به خاطر بسپار" @@ -3538,13 +3538,13 @@ "message": "و به ساختن حساب‌تان ادامه دهید." }, "noEmail": { - "message": "No email?" + "message": "ایمیلی دریافت نکردید؟" }, "goBack": { - "message": "Go back" + "message": "بازگشت به عقب" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "برای ویرایش آدرس ایمیل خود." }, "eu": { "message": "اروپا", @@ -3578,41 +3578,49 @@ "message": "ایمیل کاربر وجود ندارد" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "ایمیل کاربر فعال پیدا نشد. در حال خارج کردن شما از سیستم هستیم." }, "deviceTrusted": { "message": "دستگاه مورد اعتماد است" }, "trustOrganization": { - "message": "Trust organization" + "message": "اعتماد به سازمان" }, "trust": { - "message": "Trust" + "message": "اطمینان" }, "doNotTrust": { - "message": "Do not trust" + "message": "اعتماد نکنید" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "سازمان مورد اعتماد نیست" }, "emergencyAccessTrustWarning": { - "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" + "message": "برای امنیت حساب کاربری شما، فقط در صورتی تأیید کنید که دسترسی اضطراری به این کاربر داده‌اید و اثر انگشت او با آنچه در حسابش نمایش داده شده، مطابقت دارد" }, "orgTrustWarning": { - "message": "For the security of your account, only proceed if you are a member of this organization, have account recovery enabled, and the fingerprint displayed below matches the organization's fingerprint." + "message": "برای امنیت حساب کاربری شما، فقط در صورتی ادامه دهید که عضو این سازمان باشید، بازیابی حساب کاربری را فعال کرده باشید و اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت داشته باشد." }, "orgTrustWarning1": { - "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + "message": "این سازمان دارای سیاست سازمانی است که شما را در بازیابی حساب کاربری ثبت‌نام می‌کند. ثبت‌نام به مدیران سازمان اجازه می‌دهد کلمه عبور شما را تغییر دهند. فقط در صورتی ادامه دهید که این سازمان را می‌شناسید و عبارت اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت دارد." }, "trustUser": { - "message": "Trust user" + "message": "به کاربر اعتماد کنید" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "ارسال‌های فعالی نیست", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "message": "از ارسال برای اشتراک‌گذاری امن اطلاعات رمزگذاری شده با هر کسی استفاده کنید.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsTitleNoItems": { + "message": "اطلاعات حساس را به‌صورت ایمن ارسال کنید", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "پرونده‌ها و داده‌های خود را به‌صورت امن با هر کسی، در هر پلتفرمی به اشتراک بگذارید. اطلاعات شما در حین اشتراک‌گذاری به‌طور کامل رمزگذاری انتها به انتها باقی خواهد ماند و میزان افشا محدود می‌شود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3689,10 +3697,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "۱ فیلد به توجه شما نیاز دارد." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "فیلدهای $COUNT$ به توجه شما نیاز دارند.", "placeholders": { "count": { "content": "$1", @@ -3747,53 +3755,53 @@ "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "تغییر وضعیت ناوبری کناری" }, "skipToContent": { - "message": "Skip to content" + "message": "پرش به محتوا" }, "bitwardenOverlayButton": { - "message": "Bitwarden autofill menu button", + "message": "دکمه منوی پر کردن خودکار Bitwarden", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Toggle Bitwarden autofill menu", + "message": "نمایش یا مخفی کردن منوی پر کردن خودکار Bitwarden", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "منوی پر کردن خودکار Bitwarden", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "برای مشاهده ورودهای منطبق، قفل حساب کاربری خود را باز کنید", "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "برای مشاهده پیشنهادهای پر کردن خودکار، قفل حساب کاربری خود را باز کنید", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { - "message": "Unlock account", + "message": "قفل حساب کاربری را باز کنید", "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "قفل حساب کاربری خود را باز کنید، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "کد تأیید رمز یک‌بار مصرف مبتنی بر زمان", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "زمان باقی‌مانده تا انقضای کد TOTP فعلی", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { - "message": "Fill credentials for", + "message": "پر کردن اطلاعات ورود برای", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { - "message": "Partial username", + "message": "نام کاربری جزئی", "description": "Screen reader text for when a login item is focused where a partial username is displayed. SR will announce this phrase before reading the text of the partial username" }, "noItemsToShow": { @@ -3809,31 +3817,31 @@ "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { - "message": "New login", + "message": "ورود جدید", "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "افزودن مورد ورود جدید به گاوصندوق، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "کارت جدید", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "افزودن کارت جدید به گاوصندوق، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "هویت جدید", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Add new vault identity item, opens in a new window", + "message": "افزودن هویت جدید به گاوصندوق، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { - "message": "Bitwarden autofill menu available. Press the down arrow key to select.", + "message": "منوی پر کردن خودکار Bitwarden در دسترس است. برای انتخاب، کلید فلش پایین را فشار دهید.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { @@ -3847,13 +3855,13 @@ "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "خطای وارد کردن" + "message": "خطای درون ریزی" }, "importErrorDesc": { "message": "مشکلی با داده‌هایی که سعی کردید وارد کنید وجود داشت. لطفاً خطاهای فهرست شده زیر را در فایل منبع خود برطرف کرده و دوباره امتحان کنید." }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "خطاهای زیر را برطرف کرده و دوباره امتحان کنید." }, "description": { "message": "توضیحات" @@ -3874,10 +3882,10 @@ "message": "دوباره سعی کنید" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "برای این اقدام تأیید لازم است. یک کد پین تعیین کنید تا ادامه دهید." }, "setPin": { - "message": "تنظیم PIN" + "message": "تنظیم کد پین" }, "verifyWithBiometrics": { "message": "تایید با استفاده از بیومتریک" @@ -3892,25 +3900,25 @@ "message": "نیازمند روش دیگری هستید؟" }, "useMasterPassword": { - "message": "استفاده از رمز عبور اصلی" + "message": "استفاده از کلمه عبور اصلی" }, "usePin": { - "message": "استفاده از PIN" + "message": "استفاده از کد پین" }, "useBiometrics": { "message": "استفاده از بیومتریک" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "کد تأییدی را که به ایمیل شما ارسال شده است وارد کنید." }, "resendCode": { - "message": "Resend code" + "message": "ارسال دوباره کد" }, "total": { "message": "مجموع" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "شما در حال درون ریزی داده به $ORGANIZATION$ هستید. داده‌های شما ممکن است با اعضای این سازمان به اشتراک گذاشته شود. آیا می‌خواهید ادامه دهید؟", "placeholders": { "organization": { "content": "$1", @@ -3919,13 +3927,13 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "خطا در اتصال به سرویس Duo. از روش ورود دو مرحله‌ای دیگری استفاده کنید یا برای دریافت کمک با Duo تماس بگیرید." }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "ورود دو مرحله ای Duo برای حساب کاربری شما لازم است." }, "popoutExtension": { - "message": "Popout extension" + "message": "باز کردن پنجره جداگانه افزونه" }, "launchDuo": { "message": "اجرای Duo" @@ -3937,25 +3945,25 @@ "message": "چیزی وارد نشد." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "خطا در رمزگشایی پرونده‌ی درون ریزی شده. کلید رمزگذاری شما با کلید رمزگذاری استفاده شده برای درون ریزی داده‌ها مطابقت ندارد." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "کلمه عبور پرونده نامعتبر است، لطفاً از کلمه عبوری که هنگام ایجاد پرونده‌ی برون ریزی وارد کردید استفاده کنید." }, "destination": { - "message": "Destination" + "message": "مقصد" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "درباره گزینه‌های برون ریزی خود بیاموزید" }, "selectImportFolder": { "message": "یک پوشه انتخاب کنید" }, "selectImportCollection": { - "message": "Select a collection" + "message": "انتخاب یک مجموعه" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "اگر می‌خواهید محتوای فایل وارد شده به $DESTINATION$ منتقل شود، این گزینه را انتخاب کنید", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -3965,25 +3973,25 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "پرونده حاوی موارد اختصاص نیافته است." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "فرمت پرونده‌ی درون ریزی را انتخاب کنید" }, "selectImportFile": { - "message": "Select the import file" + "message": "پرونده‌ی درون ریزی را انتخاب کنید" }, "chooseFile": { - "message": "Choose File" + "message": "انتخاب پرونده" }, "noFileChosen": { - "message": "No file chosen" + "message": "هیچ پرونده‌ای انتخاب نشد" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "یا محتویات پرونده‌ی درون ریزی را کپی/پیست کنید" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "دستورالعمل‌های $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -3993,25 +4001,25 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "درون ریزی گاوصندوق را تأیید کنید" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "این پرونده با کلمه عبور محافظت شده است. لطفاً کلمه عبور پرونده را برای درون ریزی داده‌ها وارد کنید." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "تأیید کلمه عبور پرونده" }, "exportSuccess": { - "message": "Vault data exported" + "message": "داده های گاوصندوق برون ریزی شد" }, "typePasskey": { - "message": "Passkey" + "message": "کلید عبور" }, "accessing": { - "message": "Accessing" + "message": "در حال دسترسی" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "وارد شده!" }, "passkeyNotCopied": { "message": "کلید عبور کپی نمی‌شود" @@ -4023,7 +4031,7 @@ "message": "تأیید توسط سایت آغازگر الزامی است. این ویژگی هنوز برای حساب‌های بدون کلمه عبور اصلی اجرا نشده است." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "با کلید عبور وارد می‌شوید؟" }, "passkeyAlreadyExists": { "message": "یک کلید عبور از قبل برای این برنامه وجود دارد." @@ -4035,10 +4043,10 @@ "message": "شما هیچ ورود مشابهی برای این سایت ندارید." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "ورود منطبق برای این سایت یافت نشد" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "جستجو یا ذخیره کلید عبور به عنوان ورود جدید" }, "confirm": { "message": "تأیید" @@ -4050,10 +4058,10 @@ "message": "کلید عبور را به عنوان ورود جدید ذخیره کن" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "یک ورود برای ذخیره این کلید عبور انتخاب کنید" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "یک کلید عبور برای ورود انتخاب کنید" }, "passkeyItem": { "message": "مورد کلید عبور" @@ -4071,125 +4079,125 @@ "message": "برای استفاده از کلید عبور، احراز هویت لازم است. برای ادامه، هویت خود را تأیید کنید." }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "تأیید هویت چند مرحله‌ای کنسل شد" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "هیچ داده‌ای از LastPass یافت نشد" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "نام کاربری یا کلمه عبور اشتباه است" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "کلمه عبور اشتباه است" }, "incorrectCode": { - "message": "Incorrect code" + "message": "کد اشتباه است" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "کد پین نادرست است" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "احراز هویت چند مرحله‌ای ناموفق بود" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "شامل پوشه‌های به‌ اشتراک گذاری‌ شده" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "ایمیل LastPass" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "در حال درون ریزی حساب کاربری شما..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "احراز هویت چند مرحله‌ای LastPass مورد نیاز است" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "کد یک‌بار مصرف خود را از برنامه احراز هویت وارد کنید" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "درخواست ورود را در برنامه احراز هویت خود تأیید کنید یا یک کد یک‌بار مصرف وارد کنید." }, "passcode": { - "message": "Passcode" + "message": "کد عبور" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "کلمه عبور اصلی LastPass" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "احراز هویت LastPass مورد نیاز است" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "در انتظار احراز هویت SSO" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "لطفاً برای ورود، از اطلاعات کاربری شرکت خود استفاده کنید." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "دستورالعمل‌های کامل را در سایت راهنمای ما مشاهده کنید در", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "درون ریزی مستقیم از LastPass" }, "importFromCSV": { - "message": "Import from CSV" + "message": "درون ریزی از CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "دوباره تلاش کنید یا ایمیلی از LastPass را بررسی کنید تا هویت شما تأیید شود." }, "collection": { - "message": "Collection" + "message": "مجموعه" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "کلید YubiKey مربوط به حساب کاربری LastPass خود را در درگاه USB کامپیوتر قرار دهید، سپس دکمه آن را لمس کنید." }, "switchAccount": { - "message": "Switch account" + "message": "تعویض حساب کاربری" }, "switchAccounts": { - "message": "Switch accounts" + "message": "تعویض حساب‌ها" }, "switchToAccount": { - "message": "Switch to account" + "message": "تعویض به حساب کاربری" }, "activeAccount": { - "message": "Active account" + "message": "حساب کاربری فعال" }, "bitwardenAccount": { - "message": "Bitwarden account" + "message": "حساب کاربری Bitwarden" }, "availableAccounts": { - "message": "Available accounts" + "message": "حساب کاربری در درسترس" }, "accountLimitReached": { - "message": "Account limit reached. Log out of an account to add another." + "message": "محدودیت حساب کاربری تکمیل شد. برای افزودن حساب کاربری دیگر، از یک حساب خارج شوید." }, "active": { - "message": "active" + "message": "فعال" }, "locked": { - "message": "locked" + "message": "قفل شد" }, "unlocked": { - "message": "unlocked" + "message": "باز شد" }, "server": { - "message": "server" + "message": "سرور" }, "hostedAt": { - "message": "hosted at" + "message": "میزبانی شده در" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "از دستگاه یا کلید سخت‌افزاری خود استفاده کنید" }, "justOnce": { - "message": "Just once" + "message": "فقط یک بار" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "همیشه برای این سایت" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "دامنه $DOMAIN$ به دامنه‌های مستثنی اضافه شد.", "placeholders": { "domain": { "content": "$1", @@ -4198,106 +4206,106 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "فرمت‌های رایج", "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "ادامه به تنظیمات مرورگر؟", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "به مرکز راهنمایی ادامه می‌دهید؟", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "تنظیمات پر کردن خودکار و مدیریت کلمه عبور مرورگر خود را تغییر دهید.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "می‌توانید میان‌برهای افزونه را در تنظیمات مرورگر خود مشاهده و تنظیم کنید.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "تنظیمات پر کردن خودکار و مدیریت کلمه عبور مرورگر خود را تغییر دهید.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "می‌توانید میان‌برهای افزونه را در تنظیمات مرورگر خود مشاهده و تنظیم کنید.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "می‌خواهید Bitwarden را مدیر کلمه عبور پیش‌فرض خود کنید؟", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", + "message": "نادیده گرفتن این گزینه ممکن است باعث تداخل بین پیشنهادهای پر کردن خودکار Bitwarden و مرورگر شما شود.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "Bitwarden را به عنوان مدیر کلمه عبور پیش‌فرض خود تنظیم کنید", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "امکان تنظیم Bitwarden به‌عنوان مدیر کلمه عبور پیش‌فرض وجود ندارد", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.", + "message": "برای تنظیم Bitwarden به‌عنوان مدیر کلمه عبور پیش‌فرض، باید دسترسی‌های حفظ حریم خصوصی مرورگر را به آن بدهید.", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "تنظیم به‌عنوان پیش‌فرض", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "اطلاعات ورود با موفقیت ذخیره شد!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "کلمه عبور ذخیره شد!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "اطلاعات ورود با موفقیت به‌روزرسانی شد!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "کلمه عبور به‌روزرسانی شد!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "خطا در ذخیره اطلاعات ورود. جزئیات را در کنسول بررسی کنید.", "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "موفقیت آمیز بود" }, "removePasskey": { - "message": "Remove passkey" + "message": "حذف کلید عبور" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "کلید عبور حذف شد" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "پیشنهادهای پر کردن خودکار" }, "itemSuggestions": { - "message": "Suggested items" + "message": "موارد پیشنهادی" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "یک مورد ورود برای این سایت ذخیره کنید تا به‌صورت خودکار پر شود" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "گاوصندوق‌تان خالی است" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "موردی با جستجوی شما مطابقت نداشت" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "فیلترها را پاک کنید یا عبارت جستجوی دیگری را امتحان کنید" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "کپی اطلاعات - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4307,7 +4315,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "کپی یادداشت - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4317,7 +4325,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "گزینه‌های بیشتر، $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4327,7 +4335,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "گزینه‌های بیشتر - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4337,7 +4345,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "مشاهده آیتم - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4347,7 +4355,7 @@ } }, "viewItemTitleWithField": { - "message": "View item - $ITEMNAME$ - $FIELD$", + "message": "مشاهده مورد - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4361,7 +4369,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "پر کردن خودکار - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4371,7 +4379,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "پر کردن خودکار - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4385,7 +4393,7 @@ } }, "copyFieldValue": { - "message": "Copy $FIELD$, $VALUE$", + "message": "کپی $FIELD$، $VALUE$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -4399,40 +4407,40 @@ } }, "noValuesToCopy": { - "message": "No values to copy" + "message": "هیچ مقداری برای کپی وجود ندارد" }, "assignToCollections": { - "message": "Assign to collections" + "message": "اختصاص به مجموعه‌ها" }, "copyEmail": { - "message": "Copy email" + "message": "کپی ایمیل" }, "copyPhone": { - "message": "Copy phone" + "message": "کپی تلفن" }, "copyAddress": { - "message": "Copy address" + "message": "کپی آدرس" }, "adminConsole": { - "message": "Admin Console" + "message": "کنسول مدیر" }, "accountSecurity": { - "message": "Account security" + "message": "امنیت حساب کاربری" }, "notifications": { - "message": "Notifications" + "message": "اعلان‌ها" }, "appearance": { - "message": "Appearance" + "message": "ظاهر" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "خطا در اختصاص مجموعه هدف." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "خطا در اختصاص پوشه هدف." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "مشاهده موارد در $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4442,7 +4450,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "بازگشت به $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4452,10 +4460,10 @@ } }, "new": { - "message": "New" + "message": "جدید" }, "removeItem": { - "message": "Remove $NAME$", + "message": "حذف $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4465,56 +4473,56 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "موارد بدون پوشه" }, "itemDetails": { - "message": "Item details" + "message": "جزئیات مورد" }, "itemName": { - "message": "Item name" + "message": "نام مورد" }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "سازمان غیرفعال شده است" }, "owner": { - "message": "Owner" + "message": "مالک" }, "selfOwnershipLabel": { - "message": "You", + "message": "شما", "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." + "message": "به موردهای موجود در سازمان‌های غیرفعال نمی‌توان دسترسی داشت. برای دریافت کمک با مالک سازمان تماس بگیرید." }, "additionalInformation": { - "message": "Additional information" + "message": "اطلاعات بیشتر" }, "itemHistory": { - "message": "Item history" + "message": "تاریخچه مورد" }, "lastEdited": { - "message": "Last edited" + "message": "آخرین ویرایش" }, "ownerYou": { - "message": "Owner: You" + "message": "مالک: شما" }, "linked": { - "message": "Linked" + "message": "پیوند شده" }, "copySuccessful": { - "message": "Copy Successful" + "message": "کپی موفق بود" }, "upload": { - "message": "Upload" + "message": "بارگذاری" }, "addAttachment": { - "message": "Add attachment" + "message": "افزودن پیوست" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "بیشترین حجم پرونده ۵۰۰ مگابایت است" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "حذف پیوست $NAME$", "placeholders": { "name": { "content": "$1", @@ -4523,7 +4531,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "بارگیری $NAME$", "placeholders": { "name": { "content": "$1", @@ -4532,52 +4540,52 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "بارگیری Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "Bitwarden را روی همه دستگاه‌ها بارگیری کنید" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "دریافت برنامه‌ی تلفن همراه" }, "getTheMobileAppDesc": { - "message": "Access your passwords on the go with the Bitwarden mobile app." + "message": "با برنامه موبایل Bitwarden، به کلمات عبور خود در هر زمان و مکان دسترسی داشته باشید." }, "getTheDesktopApp": { - "message": "Get the desktop app" + "message": "برنامه دسکتاپ را دریافت کنید" }, "getTheDesktopAppDesc": { - "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + "message": "بدون استفاده از مرورگر به گاوصندوق خود دسترسی پیدا کنید و سپس باز کردن قفل با بیومتریک را تنظیم کنید تا باز کردن سریع‌تر قفل در اپ دسکتاپ و افزونه مرورگر انجام شود." }, "downloadFromBitwardenNow": { - "message": "Download from bitwarden.com now" + "message": "هم‌اکنون از bitwarden.com بارگیری کنید" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "از Google Play دریافت کنید" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "از AppStore بارگیری کنید" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "آیا مطمئن هستید که می‌خواهید این پرونده پیوست را به‌طور دائمی حذف کنید؟" }, "premium": { - "message": "Premium" + "message": "پرمیوم" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "سازمان‌های رایگان نمی‌توانند از پرونده‌های پیوست استفاده کنند" }, "filters": { - "message": "Filters" + "message": "فیلترها" }, "filterVault": { - "message": "Filter vault" + "message": "فیلتر گاوصندوق" }, "filterApplied": { - "message": "One filter applied" + "message": "یک فیلتر اعمال شده است" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "$COUNT$ فیلتر اعمال شده است", "placeholders": { "count": { "content": "$1", @@ -4586,16 +4594,16 @@ } }, "personalDetails": { - "message": "Personal details" + "message": "جزئیات شخصی" }, "identification": { - "message": "Identification" + "message": "شناسایی" }, "contactInfo": { - "message": "Contact info" + "message": "اطلاعات مخاطب" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "بارگیری - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4604,23 +4612,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "شماره کارت پایان می‌یابد با", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login credentials" + "message": "اطلاعات ورود" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "کلید احراز هویت" }, "autofillOptions": { - "message": "Autofill options" + "message": "گزینه‌های پر کردن خودکار" }, "websiteUri": { - "message": "Website (URI)" + "message": "وب‌سایت (نشانی اینترنتی)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "وب‌سایت (نشانی اینترنتی) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -4630,16 +4638,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "وب‌سایت اضافه شد" }, "addWebsite": { - "message": "Add website" + "message": "افزودن وب‌سایت" }, "deleteWebsite": { - "message": "Delete website" + "message": "حذف وبسایت" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "پیش‌فرض ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4649,7 +4657,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "نمایش تطبیق سایت $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4658,7 +4666,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "مخفی کردن تطبیق سایت $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4667,19 +4675,19 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "پر کردن خودکار هنگام بارگذاری صفحه؟" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "تاریخ کارت منقضی شده است" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "اگر تمدید کرده‌اید، اطلاعات کارت را به‌روزرسانی کنید" }, "cardDetails": { - "message": "Card details" + "message": "جزئیات کارت" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$ جزئیات", "placeholders": { "brand": { "content": "$1", @@ -4688,43 +4696,43 @@ } }, "enableAnimations": { - "message": "Enable animations" + "message": "فعال‌سازی انیمیشن‌ها" }, "showAnimations": { - "message": "Show animations" + "message": "نمایش انیمیشن‌ها" }, "addAccount": { - "message": "Add account" + "message": "افزودن حساب کاربری" }, "loading": { - "message": "Loading" + "message": "در حال بارگذاری" }, "data": { - "message": "Data" + "message": "داده‌" }, "passkeys": { - "message": "Passkeys", + "message": "کلیدهای عبور", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "کلمات عبور", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "با کلید عبور وارد شوید", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "اختصاص بدهید" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "فقط اعضای سازمانی که به این مجموعه‌ها دسترسی دارند قادر به مشاهده این مورد خواهند بود." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "فقط اعضای سازمانی که به این مجموعه‌ها دسترسی دارند قادر به مشاهده این موارد خواهند بود." }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "شما $TOTAL_COUNT$ مورد را انتخاب کرده‌اید. نمی‌توانید $READONLY_COUNT$ مورد را به‌روزرسانی کنید زیرا دسترسی ویرایش ندارید.", "placeholders": { "total_count": { "content": "$1", @@ -4736,37 +4744,37 @@ } }, "addField": { - "message": "Add field" + "message": "افزودن فیلد" }, "add": { - "message": "Add" + "message": "افزودن" }, "fieldType": { - "message": "Field type" + "message": "نوع فیلد" }, "fieldLabel": { - "message": "Field label" + "message": "برچسب فیلد" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "برای داده‌هایی مانند سوالات امنیتی از فیلدهای متنی استفاده کنید" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "برای داده‌های حساس مانند کلمه عبور از فیلدهای مخفی استفاده کنید" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "اگر می‌خواهید فیلدهای تیک‌دار فرم را به‌صورت خودکار پر کنید، مانند گزینه به یاد سپردن ایمیل، از کادرهای انتخاب استفاده کنید" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "وقتی در پر کردن خودکار برای یک وب‌سایت خاص به مشکل برخوردید، از فیلد مرتبط استفاده کنید." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "شناسه Html، نام، aria-label یا محل نگهدار فیلد را وارد کنید." }, "editField": { - "message": "Edit field" + "message": "ویرایش فیلد" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "ویرایش $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4775,7 +4783,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "حذف $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4784,7 +4792,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ افزوده شد", "placeholders": { "label": { "content": "$1", @@ -4793,7 +4801,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "مرتب‌سازی مجدد $LABEL$. برای جابجایی مورد به بالا یا پایین از کلیدهای جهت‌نما استفاده کنید.", "placeholders": { "label": { "content": "$1", @@ -4802,10 +4810,10 @@ } }, "reorderWebsiteUriButton": { - "message": "Reorder website URI. Use arrow key to move item up or down." + "message": "مرتب‌سازی مجدد نشانی اینترنتی وب‌سایت. برای جابجایی مورد به بالا یا پایین از کلیدهای جهت‌نما استفاده کنید." }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ به بالا منتقل شد، موقعیت $INDEX$ از $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4822,13 +4830,13 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "مجموعه‌ها را برای اختصاص انتخاب کنید" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "۱ مورد به طور دائمی به سازمان انتخاب شده منتقل خواهد شد. شما دیگر مالک این مورد نخواهید بود." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ مورد به طور دائمی به سازمان انتخاب شده منتقل خواهند شد. شما دیگر مالک این موارد نخواهید بود.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4837,7 +4845,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "۱ مورد به طور دائمی به $ORG$ منتقل خواهد شد. شما دیگر مالک این مورد نخواهید بود.", "placeholders": { "org": { "content": "$1", @@ -4846,7 +4854,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ مورد به طور دائمی به $ORG$ منتقل خواهند شد. شما دیگر مالک این موارد نخواهید بود.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4859,13 +4867,13 @@ } }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "مجموعه‌ها با موفقیت اختصاص داده شدند" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "شما چیزی را انتخاب نکرده اید." }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "موارد به $ORGNAME$ منتقل شدند", "placeholders": { "orgname": { "content": "$1", @@ -4874,7 +4882,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "مورد به $ORGNAME$ منتقل شد", "placeholders": { "orgname": { "content": "$1", @@ -4883,7 +4891,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ به پایین منتقل شد، موقعیت $INDEX$ از $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4900,46 +4908,46 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "مکان مورد" }, "fileSend": { - "message": "File Send" + "message": "پرونده ارسال" }, "fileSends": { - "message": "File Sends" + "message": "پرونده ارسال‌ها" }, "textSend": { - "message": "Text Send" + "message": "ارسال متن" }, "textSends": { - "message": "Text Sends" + "message": "ارسال‌های متن" }, "accountActions": { - "message": "Account actions" + "message": "فعالیت‌های حساب کاربری" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "نمایش تعداد پیشنهادهای پر کردن خودکار ورود در آیکون افزونه" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "نمایش عملیات کپی سریع در گاوصندوق" }, "systemDefault": { - "message": "System default" + "message": "پیش‌فرض سیستم" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "الزامات سیاست سازمانی روی این تنظیم اعمال شده است" }, "sshPrivateKey": { - "message": "Private key" + "message": "کلید خصوصی" }, "sshPublicKey": { - "message": "Public key" + "message": "کلید عمومی" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "اثر انگشت" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "نوع کلید" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4954,61 +4962,61 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "تلاش مجدد" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "حداقل مهلت زمانی سفارشی ۱ دقیقه است." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "محتوای اضافی در دسترس است" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "پرونده در دستگاه ذخیره شد. از بخش بارگیری‌های دستگاه خود مدیریت کنید." }, "showCharacterCount": { - "message": "Show character count" + "message": "نمایش تعداد کاراکترها" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "مخفی کردن تعداد کاراکترها" }, "itemsInTrash": { - "message": "Items in trash" + "message": "موراد در سطل زباله" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "موردی در سطل زباله نیست" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "مواردی که حذف می‌کنید اینجا نمایش داده می‌شوند و پس از ۳۰ روز به طور دائمی حذف خواهند شد" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "مواردی که بیش از ۳۰ روز در سطل زباله بوده‌اند به‌طور خودکار حذف خواهند شد" }, "restore": { - "message": "Restore" + "message": "بازیابی" }, "deleteForever": { - "message": "Delete forever" + "message": "حذف برای همیشه" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "شما مجوز ویرایش این مورد را ندارید" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "بازکردن با بیومتریک در دسترس نیست زیرا ابتدا باید بازکردن قفل با کد پین یا کلمه عبور انجام شود." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "باز کردن قفل با بیومتریک در حال حاضر در دسترس نیست." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "باز کردن قفل با بیومتریک به دلیل پیکربندی نادرست پرونده‌های سیستم در دسترس نیست." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "باز کردن قفل با بیومتریک به دلیل پیکربندی نادرست پرونده‌های سیستم در دسترس نیست." }, "biometricsStatusHelptextDesktopDisconnected": { - "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + "message": "باز کردن قفل با بیومتریک در دسترس نیست زیرا برنامه دسکتاپ Bitwarden بسته است." }, "biometricsStatusHelptextNotEnabledInDesktop": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "باز کردن قفل با بیومتریک در دسترس نیست زیرا برای $EMAIL$ در برنامه دسکتاپ Bitwarden فعال نشده است.", "placeholders": { "email": { "content": "$1", @@ -5017,208 +5025,217 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "باز کردن قفل با بیومتریک در حال حاضر به دلیل نامعلومی در دسترس نیست." + }, + "unlockVault": { + "message": "قفل گاوصندوق خود را در چند ثانیه باز کنید" + }, + "unlockVaultDesc": { + "message": "می‌توانید تنظیمات باز کردن قفل و زمان‌بندی خودکار بسته شدن گاوصندوق را سفارشی کنید تا سریع‌تر به گاوصندوق خود دسترسی پیدا کنید." + }, + "unlockPinSet": { + "message": "بازکردن قفل کد پین تنظیم شد" }, "authenticating": { - "message": "Authenticating" + "message": "در حال احراز هویت" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "پر کردن کلمه عبور تولید شده", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "کلمه عبور تولید شد", "description": "Notification message for when a password has been regenerated" }, "saveToBitwarden": { - "message": "Save to Bitwarden", + "message": "ذخیره در Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "فاصله", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "تیلد", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "بک‌تیک", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "علامت تعجب", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "علامت @", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "علامت #", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "علامت دلار", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "علامت ٪", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "علامت ^", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "علامت &", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "علامت *", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "پرانتز چپ", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "پرانتز راست", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "خط زیر", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "خط تیره", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "به علاوه", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "مساوی", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "آکولادِ چپ", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "آکولادِ راست", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "کروشه‌ی چپ", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "کروشه‌ی راست", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "علامت |", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "بک اسلش", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "دو نقطه", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "نقطه ویرگول", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "دابل کوت", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "سینگل کوت", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "کمتر از", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "بیش‌تر از", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "کاما", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "دوره", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "علامت سوال", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "ممیز", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "حروف کوچک" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "حروف بزرگ" }, "generatedPassword": { - "message": "Generated password" + "message": "کلمه عبور تولید شد" }, "compactMode": { - "message": "Compact mode" + "message": "حالت فشرده" }, "beta": { - "message": "Beta" + "message": "آزمایشی" }, "extensionWidth": { - "message": "Extension width" + "message": "عرض افزونه" }, "wide": { - "message": "Wide" + "message": "عریض" }, "extraWide": { - "message": "Extra wide" + "message": "خیلی عریض" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "کلمه عبور وارد شده اشتباه است." }, "importSshKey": { - "message": "Import" + "message": "درون ریزی" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "تأیید کلمه عبور" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "کلمه عبور کلید SSH را وارد کنید." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "کلمه عبور را وارد کنید" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "کلید SSH نامعتبر است" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "نوع کلید SSH پشتیبانی نمی‌شود" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "وارد کردن کلید از حافظه موقت" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "کلید SSH با موفقیت وارد شد" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "نمی‌توانید مجموعه‌هایی را که فقط دسترسی مشاهده دارند حذف کنید: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -5227,121 +5244,138 @@ } }, "updateDesktopAppOrDisableFingerprintDialogTitle": { - "message": "Please update your desktop application" + "message": "لطفاً برنامه دسکتاپ خود را به‌روزرسانی کنید" }, "updateDesktopAppOrDisableFingerprintDialogMessage": { - "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + "message": "برای استفاده از باز کردن قفل با بیومتریک، لطفاً برنامه دسکتاپ خود را به‌روزرسانی کنید یا باز کردن قفل با اثر انگشت را در تنظیمات دسکتاپ غیرفعال کنید." }, "changeAtRiskPassword": { - "message": "Change at-risk password" + "message": "تغییر کلمه عبور در معرض خطر" }, "settingsVaultOptions": { - "message": "Vault options" + "message": "گزینه‌های گاوصندوق" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "گاوصندوق تنها کلمات عبور را محافظت نمی‌کند. ورودهای امن، شناسه‌ها، کارت‌ها و یادداشت‌ها را نیز به صورت امن در اینجا ذخیره کنید." }, "introCarouselLabel": { - "message": "Welcome to Bitwarden" + "message": "به Bitwarden خوش آمدید" }, "securityPrioritized": { - "message": "Security, prioritized" + "message": "امنیت، در اولویت" }, "securityPrioritizedBody": { - "message": "Save logins, cards, and identities to your secure vault. Bitwarden uses zero-knowledge, end-to-end encryption to protect what’s important to you." + "message": "ورودها، کارت‌ها و هویت‌ها را در گاوصندوق امن خود ذخیره کنید. Bitwarden از رمزگذاری انتها به انتهای دانش صفر برای محافظت از آنچه برای شما مهم است استفاده می‌کند." }, "quickLogin": { - "message": "Quick and easy login" + "message": "ورود سریع و آسان" }, "quickLoginBody": { - "message": "Set up biometric unlock and autofill to log into your accounts without typing a single letter." + "message": "قفل بیومتریک و پر کردن خودکار را تنظیم کنید تا بدون وارد کردن حتی یک حرف وارد حساب‌های خود شوید." }, "secureUser": { - "message": "Level up your logins" + "message": "ورودهای خود را ارتقا دهید" }, "secureUserBody": { - "message": "Use the generator to create and save strong, unique passwords for all your accounts." + "message": "از تولید کننده برای ایجاد و ذخیره کلمه‌های عبور قوی و منحصر به فرد برای تمام حساب‌های کاربری خود استفاده کنید." }, "secureDevices": { - "message": "Your data, when and where you need it" + "message": "داده‌های شما، زمانی که نیاز دارید و در جایی که نیاز دارید" }, "secureDevicesBody": { - "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + "message": "کلمه‌های عبور نامحدود را در دستگاه‌های نامحدود با اپلیکیشن‌های موبایل، مرورگر و دسکتاپ Bitwarden ذخیره کنید." }, "nudgeBadgeAria": { - "message": "1 notification" + "message": "۱ اعلان" }, "emptyVaultNudgeTitle": { - "message": "Import existing passwords" + "message": "درون ریزی کلمات عبور موجود" }, "emptyVaultNudgeBody": { - "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + "message": "از ابزار درون ریزی استفاده کنید تا ورودها را سریعاً به Bitwarden منتقل کنید بدون نیاز به افزودن دستی آن‌ها." }, "emptyVaultNudgeButton": { - "message": "Import now" + "message": "الان درودن ریزی کن" }, "hasItemsVaultNudgeTitle": { - "message": "Welcome to your vault!" + "message": "به گاوصندوق خود خوش آمدید!" }, "hasItemsVaultNudgeBodyOne": { - "message": "Autofill items for the current page" + "message": "پر کردن خودکار موارد برای صفحه فعلی" }, "hasItemsVaultNudgeBodyTwo": { - "message": "Favorite items for easy access" + "message": "موارد مورد علاقه برای دسترسی آسان" }, "hasItemsVaultNudgeBodyThree": { - "message": "Search your vault for something else" + "message": "در گاوصندوق خود به دنبال چیز دیگری بگردید" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "با پر کردن خودکار در وقت خود صرفه جویی کنید" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "شامل یک", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "وب‌سایت", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "تا این ورود به عنوان پیشنهاد پر کردن خودکار ظاهر شود.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newCardNudgeTitle": { - "message": "Seamless online checkout" + "message": "پرداخت آنلاین بدون وقفه" }, "newCardNudgeBody": { - "message": "With cards, easily autofill payment forms securely and accurately." + "message": "با کارت‌ها، فرم‌های پرداخت را به‌راحتی و با امنیت و دقت پر کنید." }, "newIdentityNudgeTitle": { - "message": "Simplify creating accounts" + "message": "ساخت حساب‌های کاربری را ساده کنید" }, "newIdentityNudgeBody": { - "message": "With identities, quickly autofill long registration or contact forms." + "message": "با هویت‌ها، به سرعت فرم‌های طولانی ثبت‌نام یا تماس را پر کنید." }, "newNoteNudgeTitle": { - "message": "Keep your sensitive data safe" + "message": "اطلاعات حساس خود را ایمن نگه دارید" }, "newNoteNudgeBody": { - "message": "With notes, securely store sensitive data like banking or insurance details." + "message": "با یادداشت‌ها، اطلاعات حساسی مانند جزئیات بانکی یا بیمه را به‌صورت ایمن ذخیره کنید." }, "newSshNudgeTitle": { - "message": "Developer-friendly SSH access" + "message": "دسترسی SSH مناسب برای توسعه‌دهندگان" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "کلیدهای خود را ذخیره کنید و با عامل SSH برای احراز هویت سریع و رمزگذاری‌شده متصل شوید.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "اطلاعات بیشتر درباره عامل SSH را بیاموزید", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "ساخت سریع کلمات عبور" + }, + "generatorNudgeBodyOne": { + "message": "به‌راحتی کلمات عبور قوی و منحصر به فرد ایجاد کنید با کلیک روی", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "برای کمک به حفظ امنیت ورودهای شما.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "با کلیک روی دکمه تولید رمز عبور، به‌راحتی کلمات عبور قوی و منحصر به‌ فرد ایجاد کنید تا ورودهای شما ایمن باقی بمانند.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { - "message": "You do not have permissions to view this page. Try logging in with a different account." + "message": "شما اجازه دسترسی به این صفحه را ندارید. لطفاً با حساب کاربری دیگری وارد شوید." } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index ead59384ff8..8e5eede202a 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -1072,7 +1072,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "Uusi kohde, avautuu uudessa ikkunassa", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -1606,7 +1606,7 @@ "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." }, "turnOffBrowserAutofill": { - "message": "Turn off $BROWSER$ autofill", + "message": "Poista automaattitäyttö käytöstä selaimessa $BROWSER$", "placeholders": { "browser": { "content": "$1", @@ -1615,7 +1615,7 @@ } }, "turnOffAutofill": { - "message": "Turn off autofill" + "message": "Poista automaattitäyttö käytöstä" }, "showInlineMenuLabel": { "message": "Näytä automaattitäytön ehdotukset lomakekentissä" @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Yksilöllistä tunnistetta ei löytynyt." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ käyttää kertakirjautumista (SSO) itse ylläpidetyllä avainpalvelimella. Organisaation jäsenet eivät enää tarvitse pääsalasanaa kirjautumiseen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Poistu organisaatiosta" @@ -3584,16 +3584,16 @@ "message": "Laitteeseen luotettu" }, "trustOrganization": { - "message": "Trust organization" + "message": "Luota organisaatioon" }, "trust": { - "message": "Trust" + "message": "Luota" }, "doNotTrust": { - "message": "Do not trust" + "message": "Älä luota" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "Organisaatio ei ole luotettu" }, "emergencyAccessTrustWarning": { "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" @@ -3605,7 +3605,7 @@ "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." }, "trustUser": { - "message": "Trust user" + "message": "Luota käyttäjään" }, "sendsNoItemsTitle": { "message": "Aktiivisia Sendejä ei ole", @@ -3615,6 +3615,14 @@ "message": "Sendillä voit jakaa salattuja tietoja turvallisesti kenelle tahansa.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Syöte vaaditaan." }, @@ -4532,19 +4540,19 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "Lataa Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "Lataa Bitwarden kaikille laitteille" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "Hanki mobiilisovellus" }, "getTheMobileAppDesc": { "message": "Access your passwords on the go with the Bitwarden mobile app." }, "getTheDesktopApp": { - "message": "Get the desktop app" + "message": "Hanki työpöytäsovellus" }, "getTheDesktopAppDesc": { "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." @@ -4553,10 +4561,10 @@ "message": "Download from bitwarden.com now" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "Hanki se Google Playstä" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "Lataa App Storesta" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Haluatko varmasti poistaa liitteen pysyvästi?" @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrinen avaus ei ole tällä hetkellä käytettävissä tuntemattomasta syystä." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Todennetaan" }, @@ -5236,7 +5253,7 @@ "message": "Vaihda vaarantunut salasana" }, "settingsVaultOptions": { - "message": "Vault options" + "message": "Holvin asetukset" }, "emptyVaultDescription": { "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." @@ -5269,19 +5286,19 @@ "message": "Tallenna rajattomasti salasanoja, rajattomalla määrällä laitteita, Bitwardenin mobiili-, selain- ja työpöytäsovelluksilla." }, "nudgeBadgeAria": { - "message": "1 notification" + "message": "1 ilmoitus" }, "emptyVaultNudgeTitle": { - "message": "Import existing passwords" + "message": "Tuo olemassa olevat salasanat" }, "emptyVaultNudgeBody": { "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." }, "emptyVaultNudgeButton": { - "message": "Import now" + "message": "Tuo nyt" }, "hasItemsVaultNudgeTitle": { - "message": "Welcome to your vault!" + "message": "Tervetuloa holviisi!" }, "hasItemsVaultNudgeBodyOne": { "message": "Autofill items for the current page" @@ -5293,7 +5310,7 @@ "message": "Search your vault for something else" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "Säästä aikaa automaattitäytöllä" }, "newLoginNudgeBodyOne": { "message": "Include a", @@ -5301,7 +5318,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Verkkosivusto", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index a7734899843..e8e45249773 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Walang natagpuang natatanging nag-identipikar." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ay gumagamit ng SSO na may sariling-hosted na key server. Walang kinakailangang master password para mag-log in para sa mga miyembro ng organisasyon na ito.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Umalis sa organisasyon" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 8a825082b5f..736fd21349e 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Aucun identifiant unique trouvé." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ utilise le SSO avec un serveur de clés auto-hébergé. Un mot de passe principal n'est plus nécessaire aux membres de cette organisation pour se connecter.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Quitter l'organisation" @@ -3615,6 +3615,14 @@ "message": "Utilisez Send pour partager en toute sécurité des informations chiffrées avec tout le monde.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Saisie requise." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Le déverrouillage par biométrie n'est pas disponible actuellement pour une raison inconnue." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authentification" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 91a5121db16..e10287f3e7b 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Non se atopou ningún identificador único." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ emprega SSO con un servidor de claves propio. Os membros da organización xa non precisan dun contrasinal mestre para iniciar sesión.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Deixar a organización" @@ -3615,6 +3615,14 @@ "message": "Usar send para compartir información cifrada con quen queiras.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Requírese algunha entrada." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueo biométrico non está dispoñible por algunha razón non prevista." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticando" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 2e42a819b65..1b264d9a70a 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "לא נמצא מזהה ייחודי." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ משתמש/ת ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית כבר לא נדרשת כדי להיכנס עבור חברים של ארגון זה.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "עזוב ארגון" @@ -3615,6 +3615,14 @@ "message": "השתמש בסֵנְד כדי לשתף באופן מאובטח מידע מוצפן עם כל אחד.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "נדרש קלט." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "ביטול נעילה ביומטרי אינו זמין כעת מסיבה לא ידועה." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "מאמת" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 8af1aa70cc3..61c0fbe9963 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 557e465ea19..ad32937c740 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nije nađen jedinstveni identifikator." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ koristi jedinstvenu prijavu SSO s vlastitim poslužiteljem. Članovima organizacije glavna lozinka više nije potrebna.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Napusti organizaciju" @@ -3615,6 +3615,14 @@ "message": "Koristi Send za sigurno slanje šifriranih podataka bilo kome.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Potreban je unos." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrijsko otključavanje trenutno nije dostupno iz nepoznatog razloga." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autentifikacija" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index b47093bc2ee..50607787cd7 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nincs egyedi azonosító." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ jelenleg saját tárolású aláíráskulcsú SSO szervert használ. A mesterjelszó a továbbiakban nem szükséges a szervezeti tagsági bejelentkezéshez.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A következő szervezet tagjai számára már nincs szükség mesterjelszóra. Erősítsük meg az alábbi tartományt a szervezet adminisztrátorával." + }, + "organizationName": { + "message": "Szervezet neve" + }, + "keyConnectorDomain": { + "message": "Key Connector tartomány" }, "leaveOrganization": { "message": "Szervezet elhagyása" @@ -3615,6 +3615,14 @@ "message": "A Send használatával biztonságosan megoszthatjuk a titkosított információkat bárkivel.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Érzékeny információt küldése biztonságosan", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Fájlok vagy adatok megosztása biztonságosan bárkivel, bármilyen platformon. Az információk titkosítva maradnak a végpontokon, korlátozva a kitettséget.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Az adatbevitel kötelező." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." }, + "unlockVault": { + "message": "A széf feloldása másodpercek alatt" + }, + "unlockVaultDesc": { + "message": "Testreszabhatjuk a feloldási és időtúllépési beállításokat a széf gyorsabb elérése érdekében." + }, + "unlockPinSet": { + "message": "PIN beállítás feloldása" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Jelszavak gyors létrehozása" + }, + "generatorNudgeBodyOne": { + "message": "Könnyen létrehozhatunk erős és egyedi jelszavakat a gombra kattintva.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "a bejelentkezések biztonságának megőrzéséhez.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Könnyedén hozhatunk létre erős és egyedi jelszavakat a Jelszó generálása gombra kattintva, amely segít megőrizni a bejelentkezések biztonságát.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Nincs jogosultság az oldal megtekintéséhez. Próbáljunk meg másik fiókkal bejelentkezni." } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 03e47fca575..a12137d696a 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Tidak ada pengidentifikasi unik yang ditemukan." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ menggunakan SSO dengan server kunci yang dihosting sendiri. Kata sandi utama tidak lagi diperlukan untuk masuk untuk anggota organisasi ini.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Tinggalkan Organisasi" @@ -3615,6 +3615,14 @@ "message": "Gunakan Send untuk membagikan informasi terenkripsi secara aman dengan siapapun.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Masukan ini harus diisi." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index d3d0dc9ec43..9a5d2c65bb6 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nessun identificatore univoco trovato." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ sta usando SSO con un server self-hosted. Una password principale non è più necessaria per accedere per i membri di questa organizzazione.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lascia organizzazione" @@ -3615,6 +3615,14 @@ "message": "Utilizza un Send per condividere in modo sicuro le informazioni con qualsiasi utente.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input obbligatorio." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticazione" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index e9c22c1286f..5c235bdea43 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "一意の識別子が見つかりませんでした。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ は自己ホストの鍵サーバで SSO を使用しています。この組織のメンバーのログインにマスターパスワードは必要ありません。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "組織から脱退する" @@ -3615,6 +3615,14 @@ "message": "Send を使用すると暗号化された情報を誰とでも安全に共有できます。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "入力が必要です。" }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "生体認証によるロック解除は、不明な理由により現在利用できません。" }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "認証中" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index d8397db5da8..7f36bb3568f 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "ავთენტიკაცია" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 0459e007126..ac36d911e89 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 8add57d61ec..5a496cde98a 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "고유 식별자를 찾을 수 없습니다." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 조직은 자체 호스팅 키 서버로 SSO를 사용하고 있습니다 이 조직의 멤버들은 로그인할 때에 마스터 비밀번호를 필요로 하지 않습니다.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "조직 나가기" @@ -3615,6 +3615,14 @@ "message": "Send를 사용하여 암호화된 정보를 어느 사람과도 안전하게 공유합니다.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "입력이 필요합니다." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "인증 중" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 06ff7cda8fa..f790c226437 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "Bitwarden logotipas" }, "extName": { "message": "„Bitwarden“ slaptažodžių tvarkyklė", @@ -23,16 +23,16 @@ "message": "Sukurti paskyrą" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Pirmą kartą Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Prisijungti naudojant prieigos raktą" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Sveiki sugrįžę" }, "setAStrongPassword": { "message": "Nustatyti stiprų slaptažodį" @@ -84,7 +84,7 @@ "message": "Pagrindinio slaptažodžio užuomina (neprivaloma)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "Slaptažodžio stiprumas $SCORE$", "placeholders": { "score": { "content": "$1", @@ -93,10 +93,10 @@ } }, "joinOrganization": { - "message": "Join organization" + "message": "Prisijungti prie organizacijos" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Prisijungti prie $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -105,7 +105,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "Baigėte prisijungimą prie organizacijos nustatant pagrindinį slaptažodį." }, "tab": { "message": "Skirtukas" @@ -132,7 +132,7 @@ "message": "Kopijuoti slaptažodį" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Kopijuoti slaptažodžio frazę" }, "copyNote": { "message": "Kopijuoti pastabą" @@ -150,31 +150,31 @@ "message": "Kopijuoti saugos kodą" }, "copyName": { - "message": "Copy name" + "message": "Kopijuoti vardą" }, "copyCompany": { - "message": "Copy company" + "message": "Kopijuoti įmonę" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Kopijuoti socialinės apsaugos numerį" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopijuoti paso numerį" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopijuoti licenzijos numerį" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Kopijuoti privatų raktą" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Kopijuoti viešą raktą" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Kopijuoti piršto antspaudą" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Kopijuoti $FIELD$", "placeholders": { "field": { "content": "$1", @@ -183,13 +183,13 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Kopijuoti svetainę" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopijuoti pastabas" }, "copy": { - "message": "Copy", + "message": "Kopijuoti", "description": "Copy to clipboard" }, "fill": { @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Unikalus identifikatorius nerastas." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ naudoja SSO su savarankiškai sukurtu raktų serveriu. Pagrindinis slaptažodis nebėra reikalingas norint šios organizacijos nariams prisijungti.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Palikti organizaciją" @@ -3615,6 +3615,14 @@ "message": "Naudokite „Send“, kad saugiai bendrintumėte užšifruotą informaciją su bet kuriuo asmeniu.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index e9044f4c041..f47b5f7645e 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nav atrasts neviens neatkārtojams identifikators" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ izmanto vienoto pieteikšanos ar pašmitinātu atslēgu serveri. Tās dalībniekiem vairs nav nepieciešama galvenā parole, lai pieteiktos.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Galvenā parole vairs nav nepieciešama turpmāk minētās apvienības dalībniekiem. Lūgums saskaņot zemāk esošo domēnu ar savas apvienības pārvaldītāju." + }, + "organizationName": { + "message": "Apvienības nosaukums" + }, + "keyConnectorDomain": { + "message": "Key Connector domēns" }, "leaveOrganization": { "message": "Pamest apvienību" @@ -3615,6 +3615,14 @@ "message": "Send ir izmantojams, lai ar ikvienu droši kopīgotu šifrētu informāciju.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Drošā veidā nosūti jūtīgu informāciju", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Drošā veidā kopīgo datnes un datus ar ikvienu jebkurā platformā! Tava informācija paliks pilnībā šifrēta, vienlaikus ierobežojot riskantumu.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Jāievada vērtība." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." }, + "unlockVault": { + "message": "Atslēdz savu glabātavu dažās sekundēs" + }, + "unlockVaultDesc": { + "message": "Tu vari pielāgot savus atslēgšanas un noildzes iestatījumus, lai ātrāk piekļūtu savai glabātavai." + }, + "unlockPinSet": { + "message": "Atslēgšanas PIN iestatīts" + }, "authenticating": { "message": "Autentificē" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Ātra paroļu izveidošana" + }, + "generatorNudgeBodyOne": { + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar pogu", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", lai palīdzētu uzturērt pieteikšanās vienumus drošus.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar pogu \"Izveidot paroli\", lai palīdzētu uzturēt pieteikšanās vienumus drošus.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Nav atļaujas apskatīt šo lapu. Jāmēģina pieteikties ar citu kontu." } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index e6de809dfbd..ab0120a40e2 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index dd347d822c7..625bf093ba9 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 62b55eb6501..a11d5de6e2a 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen unik identifikator ble funnet." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruker SSO med en selvdrevet nøkkelserver. Et hovedpassord er ikke lenger nødvendig for å logge inn for medlemmer av denne organisasjonen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlat organisasjonen" @@ -3615,6 +3615,14 @@ "message": "Bruk Send til å dele kryptert informasjon med noen.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Inndata er påkrevd." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autentiserer" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index af637b2f4cd..67a784459b2 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Geen unieke id gevonden." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruikt SSO met een zelf gehoste sleutelserver. Leden van deze organisatie kunnen inloggen zonder hoofdwachtwoord.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Organisatie verlaten" @@ -3615,6 +3615,14 @@ "message": "Gebruik Send voor het veilig delen van versleutelde informatie met wie dan ook.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Gevoelige informatie veilig versturen", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Deel bestanden en gegevens veilig met iedereen, op elk platform. Je informatie blijft end-to-end versleuteld terwijl en blootstelling beperkt.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Invoer vereist." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." }, + "unlockVault": { + "message": "Ontgrendel je kluis in seconden" + }, + "unlockVaultDesc": { + "message": "Je kunt de instellingen voor ontgrendelen en time-out aanpassen om sneller toegang te krijgen tot je kluis." + }, + "unlockPinSet": { + "message": "PIN-code ontgrendelen instellen" + }, "authenticating": { "message": "Aan het inloggen" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Snel wachtwoorden maken" + }, + "generatorNudgeBodyOne": { + "message": "Maak eenvoudig sterke en unieke wachtwoorden door te klikken op", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "om je te helpen je inloggegevens veilig te houden.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Maak eenvoudig sterke en unieke wachtwoorden door op de knop Wachtwoord genereren te klikken om je logins veilig te houden.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Je hebt geen rechten om deze pagina te bekijken. Probeer in te loggen met een ander account." } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 16696e5f828..eb391f5e34b 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nie znaleziono unikatowego identyfikatora." }, - "convertOrganizationEncryptionDesc": { - "message": "Organizacja $ORGANIZATION$ używa jednokrotnego logowania SSO z własnym serwerem kluczy. Użytkownicy nie muszą logować się za pomocą hasła głównego.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hasło główne nie jest już wymagane dla członków następującej organizacji. Proszę potwierdzić poniższą domenę u administratora organizacji." + }, + "organizationName": { + "message": "Nazwa organizacji" + }, + "keyConnectorDomain": { + "message": "Domena Key Connector'a" }, "leaveOrganization": { "message": "Opuść organizację" @@ -3615,6 +3615,14 @@ "message": "Użyj wysyłki, aby bezpiecznie dzielić się zaszyfrowanymi informacjami ze wszystkimi.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Wysyłaj bezpiecznie poufne informacje", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Udostępniaj pliki i dane bezpiecznie każdemu, na każdej platformie. Twoje dane pozostaną zaszyfrowane end-to-end przy jednoczesnym ograniczeniu narażenia.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Dane wejściowe są wymagane." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Odblokowanie biometryczne jest obecnie niedostępne z nieznanego powodu." }, + "unlockVault": { + "message": "Odblokuj swój sejf w kilka sekund" + }, + "unlockVaultDesc": { + "message": "Możesz dostosować ustawienia odblokowania i limitu czasu, aby szybciej uzyskać dostęp do sejfu." + }, + "unlockPinSet": { + "message": "Ustaw kod PIN odblokowujący" + }, "authenticating": { "message": "Uwierzytelnianie" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Szybko twórz hasła" + }, + "generatorNudgeBodyOne": { + "message": "Łatwo twórz silne i unikalne hasła, klikając na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", aby pomóc Ci zachować bezpieczeństwo Twoich danych logowania.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Łatwo twórz silne i unikalne hasła, klikając na Wygeneruj Hasło, aby pomóc Ci zachować bezpieczeństwo Twoich danych logowania.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Nie masz uprawnień do przeglądania tej strony. Spróbuj zalogować się na inne konto." } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index e5ca45500a5..c0b83248edf 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nenhum identificador exclusivo encontrado." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO com um servidor de chaves auto-hospedado. Não é mais necessária uma senha mestra para os membros desta organização entrarem.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Sair da Organização" @@ -3615,6 +3615,14 @@ "message": "Use o Send para compartilhar informação criptografa com qualquer um.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Entrada necessária." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueio por biometria está indisponível por razões desconhecidas." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticando" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index a4ed095c4db..a144f5767d4 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Não foi encontrado um identificador único." }, - "convertOrganizationEncryptionDesc": { - "message": "A $ORGANIZATION$ está a utilizar o SSO com um servidor de chaves auto-hospedado. Já não é necessária uma palavra-passe mestra para iniciar sessão para os membros desta organização.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Já não é necessária uma palavra-passe mestra para os membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." + }, + "organizationName": { + "message": "Nome da organização" + }, + "keyConnectorDomain": { + "message": "Domínio do Key Connector" }, "leaveOrganization": { "message": "Sair da organização" @@ -3615,6 +3615,14 @@ "message": "Utilize o Send para partilhar de forma segura informações encriptadas com qualquer pessoa.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Envie informações sensíveis com segurança", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Partilhe ficheiros e dados de forma segura com qualquer pessoa, em qualquer plataforma. As suas informações permanecerão encriptadas ponto a ponto, limitando a exposição.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Campo obrigatório." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." }, + "unlockVault": { + "message": "Desbloqueie o seu cofre em segundos" + }, + "unlockVaultDesc": { + "message": "Pode personalizar as suas definições de desbloqueio e de tempo limite para aceder mais rapidamente ao seu cofre." + }, + "unlockPinSet": { + "message": "Definição do PIN de desbloqueio" + }, "authenticating": { "message": "A autenticar" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Criar rapidamente palavras-passe" + }, + "generatorNudgeBodyOne": { + "message": "Crie facilmente palavras-passe fortes e únicas clicando em", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "para o ajudar a manter as suas credenciais seguras.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Crie facilmente palavras-passe fortes e únicas clicando no botão Gerar palavra-passe para o ajudar a manter as suas credenciais seguras.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Não tem permissões para ver esta página. Tente iniciar sessão com uma conta diferente." } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 71990435ad2..2aabf825399 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nu a fost găsit niciun identificator unic." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ folosește SSO cu un server de chei autogăzduit. Membrii acestei organizații nu mai au nevoie de o parolă principală pentru autentificare.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Părăsire organizație" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Este necesară o intrare." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 2d23c5352e5..a23ab4172f8 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -206,7 +206,7 @@ "message": "Автозаполнение карты" }, "autoFillIdentity": { - "message": "Автозаполнение личности" + "message": "Автозаполнение личной информации" }, "fillVerificationCode": { "message": "Заполнить код подтверждения" @@ -228,7 +228,7 @@ "message": "Нет карт" }, "noIdentities": { - "message": "Нет личностей" + "message": "Нет личной информации" }, "addLoginMenu": { "message": "Добавить логин" @@ -237,7 +237,7 @@ "message": "Добавить карту" }, "addIdentityMenu": { - "message": "Добавить личность" + "message": "Добавить личную информацию" }, "unlockVaultMenu": { "message": "Разблокировать хранилище" @@ -1037,10 +1037,10 @@ "message": "Всегда показывать личности как предложения автозаполнения при просмотре хранилища" }, "showIdentitiesCurrentTab": { - "message": "Показывать Личности на вкладке" + "message": "Показывать Личную информацию на вкладке" }, "showIdentitiesCurrentTabDesc": { - "message": "Личности будут отображены на вкладке для удобного автозаполнения." + "message": "Личная информация будет отображена на вкладке для удобного автозаполнения." }, "clickToAutofillOnVault": { "message": "Кликните элементы для автозаполнения в режиме просмотра хранилища" @@ -1621,7 +1621,7 @@ "message": "Показывать предположения автозаполнения в полях формы" }, "showInlineMenuIdentitiesLabel": { - "message": "Показывать Личности как предложения" + "message": "Показывать Личную информацию как предложения" }, "showInlineMenuCardsLabel": { "message": "Показывать Карты как предложения" @@ -1699,7 +1699,7 @@ "message": "Автозаполнение последней использованной карты для текущего сайта" }, "commandAutofillIdentityDesc": { - "message": "Автозаполнение последней использованной личности для текущего сайта" + "message": "Автозаполнение последних использованных личных данных для текущего сайта" }, "commandGeneratePasswordDesc": { "message": "Сгенерировать и скопировать новый случайный пароль в буфер обмена." @@ -1857,7 +1857,7 @@ "message": "Полное имя" }, "identityName": { - "message": "Название личности" + "message": "Название личной информации" }, "company": { "message": "Компания" @@ -1989,7 +1989,7 @@ "message": "Карты" }, "identities": { - "message": "Личные данные" + "message": "Личная информация" }, "logins": { "message": "Логины" @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Уникальный идентификатор не найден." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ использует SSO с собственным сервером ключей. Для авторизации пользователям этой организации больше не требуется мастер-пароль.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Мастер-пароль больше не требуется для членов следующей организации. Пожалуйста, подтвердите указанный ниже домен у администратора вашей организации." + }, + "organizationName": { + "message": "Название организации" + }, + "keyConnectorDomain": { + "message": "Домен соединителя ключей" }, "leaveOrganization": { "message": "Покинуть организацию" @@ -3615,6 +3615,14 @@ "message": "Используйте Send для безопасного обмена зашифрованной информацией с кем угодно.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Безопасная отправка конфиденциальной информации", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Безопасно обменивайтесь файлами и данными с кем угодно на любой платформе. Ваша информация надежно шифруется и доступ к ней ограничен.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Необходимо ввести данные." }, @@ -3825,11 +3833,11 @@ "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "Новая личность", + "message": "Новая личная информация", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Добавление новой личности в хранилище, откроется в новом окне", + "message": "Добавление новой личной информации в хранилище, откроется в новом окне", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { @@ -4068,7 +4076,7 @@ "message": "Функция пока не поддерживается" }, "yourPasskeyIsLocked": { - "message": "Для использования passkey необходима аутентификация. Для продолжения работы подтвердите свою личность." + "message": "Для использования passkey необходима аутентификация. Для продолжения работы подтвердите вашу личность." }, "multifactorAuthenticationCancelled": { "message": "Многофакторная аутентификация отменена" @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." }, + "unlockVault": { + "message": "Разблокируйте свое хранилище за считанные секунды" + }, + "unlockVaultDesc": { + "message": "Вы можете настроить параметры разблокировки и тайм-аута для более быстрого доступа к хранилищу." + }, + "unlockPinSet": { + "message": "Установить PIN--код разблокировки" + }, "authenticating": { "message": "Аутентификация" }, @@ -5248,7 +5265,7 @@ "message": "Безопасность, приоритет" }, "securityPrioritizedBody": { - "message": "Сохраняйте логины, карты и личные данные в своем защищенном хранилище. Bitwarden использует сквозное шифрование, чтобы защитить то, что для вас важно." + "message": "Сохраняйте логины, карты и личную информацию в своем защищенном хранилище. Bitwarden использует сквозное шифрование, чтобы защитить то, что для вас важно." }, "quickLogin": { "message": "Быстрая и простая авторизация" @@ -5320,7 +5337,7 @@ "message": "Упрощение создания аккаунтов" }, "newIdentityNudgeBody": { - "message": "С помощью личностей можно быстро заполнять длинные регистрационные или контактные формы." + "message": "С помощью личной информации можно быстро заполнять длинные регистрационные или контактные формы." }, "newNoteNudgeTitle": { "message": "Храните ваши конфиденциальные данные в безопасности" @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Быстрое создание паролей" + }, + "generatorNudgeBodyOne": { + "message": "Легко создавайте надежные и уникальные пароли, нажатием на кнопку,", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "чтобы обеспечить безопасность ваших логинов.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Легко создавайте надежные и уникальные пароли, нажатием на кнопку 'Сгенерировать пароль', чтобы обеспечить безопасность ваших логинов.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "У вас нет прав для просмотра этой страницы. Попробуйте авторизоваться под другим аккаунтом." } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 308404946b3..eafbf9c584e 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "අද්විතීය හඳුනාගැනීමක් සොයාගත නොහැකි විය." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ස්වයං සත්කාරක යතුරු සේවාදායකයක් සමඟ SSO භාවිතා කරයි. මෙම සංවිධානයේ සාමාජිකයන් සඳහා ප්රවිෂ්ට වීමට ප්රධාන මුරපදයක් තවදුරටත් අවශ්ය නොවේ.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "සංවිධානය හැරයන්න" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index e8d2993a8df..016f4ca1f5a 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2766,7 +2766,7 @@ "message": "Nové heslo" }, "sendDisabled": { - "message": "Send zakázaný", + "message": "Send bol odstránený", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nenašiel sa žiadny jedinečný identifikátor." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používa SSO s vlastným kľúčovým serverom. Na prihlásenie členov tejto organizácie už nie je potrebné hlavné heslo.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavné heslo sa už nevyžaduje pre členov tejto organizácie. Nižšie uvedenú doménu potvrďte u správcu organizácie." + }, + "organizationName": { + "message": "Názov organizácie" + }, + "keyConnectorDomain": { + "message": "Doména Key Connectora" }, "leaveOrganization": { "message": "Opustiť organizáciu" @@ -3615,6 +3615,14 @@ "message": "Použite Send na bezpečné zdieľanie zašifrovaných informácii s kýmkoľvek.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send, citlivé informácie bezpečne", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Bezpečne zdieľajte súbory a údaje s kýmkoľvek a na akejkoľvek platforme. Vaše informácie zostanú end-to-end zašifrované a zároveň sa obmedzí ich odhalenie.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Vstup je povinný." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Odomykanie biometrickými údajmi je momentálne z neznámych dôvodov nedostupné." }, + "unlockVault": { + "message": "Odomknite trezor za pár sekúnd" + }, + "unlockVaultDesc": { + "message": "Pre rýchlejší prístup k trezoru si môžete upraviť nastavenia odomknutia a časový limit." + }, + "unlockPinSet": { + "message": "PIN na odomknutie nastavený" + }, "authenticating": { "message": "Overuje sa" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Rýchle vytváranie hesiel" + }, + "generatorNudgeBodyOne": { + "message": "Jednoducho vytvorte silné a jedinečné heslá kliknutím na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "aby ste mohli ochrániť prihlasovacie údaje.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Jednoducho vytvorte silné a jedinečné heslá kliknutím na tlačidlo Generovať heslo, aby zabezpečili prihlasovacie údaje.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Nemáte oprávnenie na zobrazenie tejto stránky. Skúste sa prihlásiť pomocou iného účtu." } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 65ee31c888c..3f84455be88 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 255b8dfb924..dae425fcfd3 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -1072,7 +1072,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "Нова ставка, отвара се у новом прозору", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -1093,15 +1093,15 @@ } }, "notificationLoginSaveConfirmation": { - "message": "saved to Bitwarden.", + "message": "сачувано у Bitwarden.", "description": "Shown to user after item is saved." }, "notificationLoginUpdatedConfirmation": { - "message": "updated in Bitwarden.", + "message": "ажурирано у Bitwarden.", "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "Одабрати $ITEMTYPE$, $ITEMNAME$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -1121,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Откључајте да бисте сачували ову пријаву", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { @@ -1600,10 +1600,10 @@ "message": "Предлог за ауто-попуњавања" }, "autofillSpotlightTitle": { - "message": "Easily find autofill suggestions" + "message": "Једноставно пронађите предлоге за ауто-пуњење" }, "autofillSpotlightDesc": { - "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + "message": "Искључите подешавања ауто-пуњења прегледача, тако да се не сукобљавај са Bitwarden." }, "turnOffBrowserAutofill": { "message": "Turn off $BROWSER$ autofill", @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Није пронађен ниједан јединствени идентификатор." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ користи SSO уз сопствени сервер за кључеве. Главна лозинка за пријаву више није неопходна за чланове ове организације.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Главна лозинка више није потребна за чланове следеће организације. Молимо потврдите домен са администратором организације." + }, + "organizationName": { + "message": "Назив организације" + }, + "keyConnectorDomain": { + "message": "Домен конектора кључа" }, "leaveOrganization": { "message": "Напусти организацију" @@ -3615,6 +3615,14 @@ "message": "Употребите Send да безбедно делите шифроване информације са било ким.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Унос је потребан." }, @@ -4532,31 +4540,31 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "Преузети Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "Преузети Bitwarden на све уређаје" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "Узети мобилну апликацију" }, "getTheMobileAppDesc": { - "message": "Access your passwords on the go with the Bitwarden mobile app." + "message": "Приступите лозинци у покрету са Bitwarden мобилном апликацијом." }, "getTheDesktopApp": { - "message": "Get the desktop app" + "message": "Узети desktop апликацију" }, "getTheDesktopAppDesc": { - "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + "message": "Приступите свом сефу без прегледача, а затим поставите откључавање са биометристима да бисте убрзали откључавање и у програму и у додатку." }, "downloadFromBitwardenNow": { - "message": "Download from bitwarden.com now" + "message": "Преузети сада са bitwarden.com" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "Набавите на Google Play" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "Преузмите са App Store" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Да ли сте сигурни да желите да трајно избришете овај прилог?" @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Биометријско откључавање није доступно из непознатог разлога." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Аутентификација" }, @@ -5269,7 +5286,7 @@ "message": "Сачувајте неограничене лозинке на неограниченим уређајима помоћу Bitwarden мобилних апликација, претраживача и десктоп апликација." }, "nudgeBadgeAria": { - "message": "1 notification" + "message": "1 нотификација" }, "emptyVaultNudgeTitle": { "message": "Увоз постојеће лозинке" @@ -5284,29 +5301,29 @@ "message": "Добродошли у ваш сеф!" }, "hasItemsVaultNudgeBodyOne": { - "message": "Autofill items for the current page" + "message": "Ауто-пуњење предмета за тренутну страницу" }, "hasItemsVaultNudgeBodyTwo": { - "message": "Favorite items for easy access" + "message": "Предмети као омиљен за лак приступ" }, "hasItemsVaultNudgeBodyThree": { - "message": "Search your vault for something else" + "message": "Претражите сеф за нешто друго" }, "newLoginNudgeTitle": { "message": "Уштедите време са ауто-пуњењем" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "Укључите", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Веб сајт", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "тако да се ова пријава појављује као предлог за ауто-пуњење.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -5332,16 +5349,33 @@ "message": "Лак SSH приступ" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "Чувајте кључеве и повежите се са SSH агент за брзу, шифровану аутентификацију.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "Сазнајте више о SSH агенту", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { - "message": "You do not have permissions to view this page. Try logging in with a different account." + "message": "Немате дозволе за преглед ове странице. Покушајте да се пријавите са другим налогом." } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 3a063f8f066..f57209122ef 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen unik identifierare hittades." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ använder SSO med en egen nyckelserver. Ett huvudlösenord krävs inte längre för att logga in för medlemmar i denna organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lämna organisation" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Inmatning är obligatoriskt." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 19430463090..fd9bac62391 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 700e9b73881..02d7d15b8a2 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Benzersiz tanımlayıcı bulunamadı." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kendi barındırdığı bir anahtar sunucusuyla SSO kullanıyor. Bu kuruluşun üyelerinin artık ana parola kullanması gerekmiyor.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Kuruluş adı" + }, + "keyConnectorDomain": { + "message": "Key Connector alan adı" }, "leaveOrganization": { "message": "Kuruluştan ayrıl" @@ -3615,6 +3615,14 @@ "message": "Şifrelenmiş bilgileri güvenle paylaşmak için Send'i kullanabilirsiniz.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Hassas bilgileri güvenle paylaşın", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Girdi gerekli." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Kasanızın kilidini saniyeler içinde açın" + }, + "unlockVaultDesc": { + "message": "Kasanıza daha hızlı ulaşmak için kilit açma ve zaman aşımı ayarlarınızı özelleştirebilirsiniz." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Kimlik doğrulanıyor" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Bu sayfayı görüntüleme izniniz yok. Farklı bir hesapla giriş yapmayı deneyin." } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index b0c78a17106..9406aabe088 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Не знайдено унікальний ідентифікатор." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ використовує SSO з власним сервером ключів. Головний пароль для учасників цієї організації більше не вимагається.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Покинути організацію" @@ -3615,6 +3615,14 @@ "message": "Використовуйте відправлення, щоб безпечно надавати доступ іншим до зашифрованої інформації.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Необхідно ввести дані." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Біометричне розблокування зараз недоступне з невідомої причини." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Аутентифікація" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 14649828aaf..29a7b7109e7 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Không tìm thấy danh tính duy nhất." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ đang sử dụng SSO với khóa máy chủ tự lưu trữ. Mật khẩu chính không còn cần để đăng nhập cho các thành viên của tổ chức này.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Rời tổ chức" @@ -3615,6 +3615,14 @@ "message": "Sử dụng Gửi để chia sẻ thông tin mã hóa một cách an toàn với bất kỳ ai.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Trường này là bắt buộc." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index e0bc34bd47d..8a537be08f2 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -1072,7 +1072,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "新增项目,在新窗口中打开", + "message": "新增项目(在新窗口中打开)", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -1101,7 +1101,7 @@ "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "选择 $ITEMTYPE$,$ITEMNAME$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "未找到唯一的标识符。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自托管密钥服务器 SSO。这个组织的成员登录时将不再需要主密码。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "以下组织的成员不再需要主密码。请与您的组织管理员确认下面的域名。" + }, + "organizationName": { + "message": "组织名称" + }, + "keyConnectorDomain": { + "message": "Key Connector 域名" }, "leaveOrganization": { "message": "退出组织" @@ -3615,6 +3615,14 @@ "message": "使用 Send 与任何人安全地分享加密信息。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "安全地发送敏感信息", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "在任何平台上安全地与任何人共享文件和数据。您的信息将在限制曝光的同时保持端到端加密。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "必须输入内容。" }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "由于某个未知的原因,生物识别解锁当前不可用。" }, + "unlockVault": { + "message": "数秒内解锁您的密码库" + }, + "unlockVaultDesc": { + "message": "您可以自定义解锁和超时设置,以便更快地访问您的密码库。" + }, + "unlockPinSet": { + "message": "解锁 PIN 设置" + }, "authenticating": { "message": "正在验证" }, @@ -5284,24 +5301,24 @@ "message": "欢迎来到您的密码库!" }, "hasItemsVaultNudgeBodyOne": { - "message": "自动填充项目用于当前页面" + "message": "为当前页面自动填充项目" }, "hasItemsVaultNudgeBodyTwo": { - "message": "收藏项目用于轻松访问" + "message": "收藏项目以便快速访问" }, "hasItemsVaultNudgeBodyThree": { - "message": "搜索密码库用于其他" + "message": "在密码库中搜索其他内容" }, "newLoginNudgeTitle": { "message": "使用自动填充节省时间" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "包含", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "网站", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "快速创建密码" + }, + "generatorNudgeBodyOne": { + "message": "一键创建强大且唯一的密码", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "帮助您保持登录安全。", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "一键创建强大且唯一的密码,帮助您保持登录安全。", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "您没有查看此页面的权限。请尝试使用其他账户登录。" } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index e9eab0a726d..9d4eabb1b49 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "找不到唯一識別碼。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自我裝載金鑰伺服器 SSO。此組織的成員登入時將不再需要主密碼。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "離開組織" @@ -3615,6 +3615,14 @@ "message": "使用 Send 可以與任何人安全地共用加密資訊。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "必須輸入內容。" }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "基於不明原因,生物辨識解鎖無法使用。" }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "驗證中" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } From 66a8c3ede3d18bde7f0a0c9165165daadea9f231 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 12:00:35 +0200 Subject: [PATCH 09/41] Autosync the updated translations (#14893) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 13 +- apps/web/src/locales/ar/messages.json | 13 +- apps/web/src/locales/az/messages.json | 13 +- apps/web/src/locales/be/messages.json | 13 +- apps/web/src/locales/bg/messages.json | 13 +- apps/web/src/locales/bn/messages.json | 13 +- apps/web/src/locales/bs/messages.json | 13 +- apps/web/src/locales/ca/messages.json | 13 +- apps/web/src/locales/cs/messages.json | 13 +- apps/web/src/locales/cy/messages.json | 13 +- apps/web/src/locales/da/messages.json | 13 +- apps/web/src/locales/de/messages.json | 13 +- apps/web/src/locales/el/messages.json | 13 +- apps/web/src/locales/en_GB/messages.json | 13 +- apps/web/src/locales/en_IN/messages.json | 13 +- apps/web/src/locales/eo/messages.json | 41 +++-- apps/web/src/locales/es/messages.json | 13 +- apps/web/src/locales/et/messages.json | 13 +- apps/web/src/locales/eu/messages.json | 13 +- apps/web/src/locales/fa/messages.json | 199 +++++++++++------------ apps/web/src/locales/fi/messages.json | 13 +- apps/web/src/locales/fil/messages.json | 13 +- apps/web/src/locales/fr/messages.json | 13 +- apps/web/src/locales/gl/messages.json | 13 +- apps/web/src/locales/he/messages.json | 13 +- apps/web/src/locales/hi/messages.json | 13 +- apps/web/src/locales/hr/messages.json | 13 +- apps/web/src/locales/hu/messages.json | 13 +- apps/web/src/locales/id/messages.json | 13 +- apps/web/src/locales/it/messages.json | 13 +- apps/web/src/locales/ja/messages.json | 13 +- apps/web/src/locales/ka/messages.json | 13 +- apps/web/src/locales/km/messages.json | 13 +- apps/web/src/locales/kn/messages.json | 13 +- apps/web/src/locales/ko/messages.json | 13 +- apps/web/src/locales/lv/messages.json | 13 +- apps/web/src/locales/ml/messages.json | 13 +- apps/web/src/locales/mr/messages.json | 13 +- apps/web/src/locales/my/messages.json | 13 +- apps/web/src/locales/nb/messages.json | 13 +- apps/web/src/locales/ne/messages.json | 13 +- apps/web/src/locales/nl/messages.json | 13 +- apps/web/src/locales/nn/messages.json | 13 +- apps/web/src/locales/or/messages.json | 13 +- apps/web/src/locales/pl/messages.json | 13 +- apps/web/src/locales/pt_BR/messages.json | 13 +- apps/web/src/locales/pt_PT/messages.json | 13 +- apps/web/src/locales/ro/messages.json | 13 +- apps/web/src/locales/ru/messages.json | 31 ++-- apps/web/src/locales/si/messages.json | 13 +- apps/web/src/locales/sk/messages.json | 17 +- apps/web/src/locales/sl/messages.json | 13 +- apps/web/src/locales/sr/messages.json | 39 ++--- apps/web/src/locales/sr_CS/messages.json | 13 +- apps/web/src/locales/sv/messages.json | 13 +- apps/web/src/locales/te/messages.json | 13 +- apps/web/src/locales/th/messages.json | 13 +- apps/web/src/locales/tr/messages.json | 13 +- apps/web/src/locales/uk/messages.json | 13 +- apps/web/src/locales/vi/messages.json | 13 +- apps/web/src/locales/zh_CN/messages.json | 27 ++- apps/web/src/locales/zh_TW/messages.json | 13 +- 62 files changed, 448 insertions(+), 634 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 152eb51d7d1..3d2c04f6673 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ongeldige bevestigingskode" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruik SSO met ’n sleutelbediener op ’n eie gasheer. ’n Hoofwagwoord word nie meer vereis vir aantekening vir lede van hierdie organisasie nie.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Verlaat organisasie" diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index c84858fce08..82907ed4df0 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "رمز التحقق غير صالح" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "مغادرة المؤسسة" diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 3518898335e..121f50f0e00 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Yararsız doğrulama kodu" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$, self-hosted açar serveri ilə SSO istifadə edir. Bu təşkilatın üzvlərinin giriş etməsi üçün artıq ana parol tələb edilməyəcək.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Aşağıdakı təşkilatların üzvləri üçün artıq ana parol tələb olunmur. Lütfən aşağıdakı domeni təşkilatınızın inzibatçısı ilə təsdiqləyin." + }, + "keyConnectorDomain": { + "message": "Key Connector domeni" }, "leaveOrganization": { "message": "Təşkilatı tərk et" diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 789f292aeaf..7409df6baff 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Памылковы праверачны код" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ выкарыстоўвае SSO з уласным серверам ключоў. Асноўны пароль для ўдзельнікаў гэтай арганізацыі больш не патрабуецца.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Выйсці з арганізацыі" diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index b26d6a21578..a8bd9d51e8f 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Грешен код за потвърждаване" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ използва еднократно удостоверяване със собствен сървър за ключове. Членовете на тази организация вече нямат нужда от главна парола за вписване.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "За членовете на следната организация вече не се изисква главна парола. Потвърдете домейна по-долу с администратора на организацията си." + }, + "keyConnectorDomain": { + "message": "Домейн на конектора за ключове" }, "leaveOrganization": { "message": "Напускане на организацията" diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index bb71e44f09d..1419d580837 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index a05e84245e8..31174ebbe5a 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index f24353d69b3..49aadd7951a 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Codi de verificació no vàlid" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ està utilitzant SSO amb un servidor autoallotjat de claus. Ja no es requereix una contrasenya mestra d'inici de sessió per als membres d'aquesta organització.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandona l'organització" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 4ba5a9c3fea..615bfa4f09b 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Neplatný ověřovací kód" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používá SSO s vlastním serverem s klíči. Hlavní heslo pro členy této organizace již není vyžadováno.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavní heslo již není vyžadováno pro členy následující organizace. Potvrďte níže uvedenou doménu u správce Vaší organizace." + }, + "keyConnectorDomain": { + "message": "Doména Key Connectoru" }, "leaveOrganization": { "message": "Opustit organizaci" diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index f91dc727a84..87c852a8869 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index f40daec488f..ee2dc4d570c 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ anvender SSO med en selv-hostet nøgleserver. Organisationsmedlemmer afkræves ikke længere en hovedadgangskode for at logge ind.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlad organisation" diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index f39bb85a082..58b4c568c61 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ungültiger Verifizierungscode" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ verwendet SSO mit einem selbst gehosteten Schlüsselserver. Ein Master-Passwort ist nicht mehr erforderlich, damit sich Mitglieder dieser Organisation anmelden können.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Für Mitglieder der folgenden Organisation ist kein Master-Passwort mehr erforderlich. Bitte bestätige die folgende Domain bei deinem Organisations-Administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector-Domain" }, "leaveOrganization": { "message": "Organisation verlassen" diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 24de28d39be..3790920f883 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ χρησιμοποιεί SSO με έναν αυτοεξυπηρετητή κλειδιών. Ένας κύριος κωδικός πρόσβασης δεν απαιτείται πλέον για να συνδεθείτε για τα μέλη αυτού του οργανισμού.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Αποχώρηση από τον οργανισμό" diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index d6a1b01f1d5..ab7bf3a1197 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organisation" diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index a0e9f4de149..736cece1de3 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organisation" diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 5b5e1f21582..0a4acdc5739 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -7446,11 +7443,11 @@ "description": "Label for a secret (key/value pair)" }, "serviceAccount": { - "message": "Servila konto", + "message": "Konto ĉe servo", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "serviceAccounts": { - "message": "Servilaj kontoj", + "message": "Kontoj ĉe servo", "description": "The title for the section that deals with service accounts." }, "secrets": { @@ -7474,7 +7471,7 @@ "description": "Title for creating a new secret." }, "newServiceAccount": { - "message": "Nova servila konto", + "message": "Nova konto ĉe servo", "description": "Title for creating a new service account." }, "secretsNoItemsTitle": { @@ -7501,7 +7498,7 @@ "description": "Placeholder text for searching secrets." }, "deleteServiceAccounts": { - "message": "Forigi servilajn kontojn", + "message": "Forigi kontojn ĉe servo", "description": "Title for the action to delete one or multiple service accounts." }, "deleteServiceAccount": { @@ -7509,7 +7506,7 @@ "description": "Title for the action to delete a single service account." }, "viewServiceAccount": { - "message": "Vidi servilan konton", + "message": "Vidi la konton ĉe servo", "description": "Action to view the details of a service account." }, "deleteServiceAccountDialogMessage": { @@ -8170,7 +8167,7 @@ "message": "This user can access Secrets Manager" }, "important": { - "message": "Grave" + "message": "Seriozaĵo:" }, "viewAll": { "message": "Vidi ĉiujn" @@ -8220,7 +8217,7 @@ "message": "Krei projekton" }, "createServiceAccount": { - "message": "Krei servilan konton" + "message": "Krei konton ĉe servo" }, "downloadThe": { "message": "Elŝuti la", @@ -9077,11 +9074,11 @@ "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { - "message": "Forĵeti maŝinan konton", + "message": "Forĵeti maŝinajn kontojn", "description": "Title for the action to delete one or multiple machine accounts." }, "deleteMachineAccount": { - "message": "Forĵeti maŝinajn kontojn", + "message": "Forĵeti maŝinan konton", "description": "Title for the action to delete a single machine account." }, "viewMachineAccount": { @@ -9580,7 +9577,7 @@ "message": "Vidi sekreton" }, "noClients": { - "message": "Estas neniu kliento por listo" + "message": "Estas neniu kliento por listi" }, "providerBillingEmailHint": { "message": "This email address will receive all invoices pertaining to this provider", @@ -9732,7 +9729,7 @@ "message": "Aldoni al dosierujo" }, "selectFolder": { - "message": "Elekti dosierujo" + "message": "Elekti dosierujon" }, "personalItemTransferWarningSingular": { "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." @@ -10102,7 +10099,7 @@ "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." }, "deleteOrganizationUser": { - "message": "Fo", + "message": "Forigi $NAME$", "placeholders": { "name": { "content": "$1", @@ -10177,7 +10174,7 @@ "message": "This action is not applicable to any of the selected members." }, "deletedSuccessfully": { - "message": "Sukcesis foriĝi" + "message": "Sukcese forigis" }, "freeFamiliesSponsorship": { "message": "Remove Free Bitwarden Families sponsorship" @@ -10246,7 +10243,7 @@ "message": "Claimed" }, "domainStatusUnderVerification": { - "message": "Sub aŭtentigo" + "message": "En konfirmado" }, "claimedDomainsDesc": { "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index a535b45e721..1660429f7f4 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Código de verificación no válido" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO con un servidor de claves autoalojado. Una contraseña maestra ya no es necesaria para iniciar sesión para los miembros de esta organización.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandonar organización" diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 9ed4db4b0e3..19608e1fd82 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Vale kinnituskood" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lahku organisatsioonist" diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 29f132852eb..b587e7f1611 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Egiaztatze-kodea ez da baliozkoa" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ostatatze propioko gako-zerbitzari batekin SSO erabiltzen ari da. Dagoeneko ez da pasahitz nagusirik behar erakunde honetako kideentzat saioa hasteko.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Utzi erakundea" diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 2fbacc8dca2..a7c07192acc 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -1,30 +1,30 @@ { "allApplications": { - "message": "All applications" + "message": "همه‌ برنامه‌ها" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "لوگو Bitwarden" }, "criticalApplications": { - "message": "Critical applications" + "message": "برنامه‌های حیاتی" }, "noCriticalAppsAtRisk": { - "message": "No critical applications at risk" + "message": "هیچ برنامه حیاتی در معرض خطر نیست" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "دسترسی به هوش مصنوعی" }, "riskInsights": { - "message": "Risk Insights" + "message": "دیدگاه‌های خطر" }, "passwordRisk": { - "message": "Password Risk" + "message": "خطر کلمه عبور" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." + "message": "کلمات عبور در معرض خطر (ضعیف، افشا شده یا تکراری) را در برنامه‌ها بررسی کنید. برنامه‌های حیاتی خود را انتخاب کنید تا اقدامات امنیتی را برای کاربران‌تان اولویت‌بندی کنید و به کلمات عبور در معرض خطر رسیدگی کنید." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "آخرین به‌روزرسانی داده‌ها: \\$DATE\\$", "placeholders": { "date": { "content": "$1", @@ -33,19 +33,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "اعضای مطلع شده" }, "revokeMembers": { - "message": "Revoke members" + "message": "حذف اعضا" }, "restoreMembers": { - "message": "Restore members" + "message": "بازیابی اعضا" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "امکان بازیابی دسترسی به سازمان وجود ندارد" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "تمام برنامه‌ها ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -54,10 +54,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "ایجاد مورد ورود جدید" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "برنامه‌های حیاتی ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -102,13 +102,13 @@ "message": "Applications marked as critical" }, "application": { - "message": "Application" + "message": "اپلیکیشن" }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "کلمات عبور در معرض خطر" }, "requestPasswordChange": { - "message": "Request password change" + "message": "درخواست تغییر کلمه عبور" }, "totalPasswords": { "message": "Total passwords" @@ -220,10 +220,10 @@ "message": "یادداشت‌ها" }, "privateNote": { - "message": "Private note" + "message": "یادداشت خصوصی" }, "note": { - "message": "Note" + "message": "یادداشت" }, "customFields": { "message": "فیلدهای سفارشی" @@ -232,19 +232,19 @@ "message": "نام صاحب کارت" }, "loginCredentials": { - "message": "Login credentials" + "message": "اطلاعات ورود" }, "personalDetails": { - "message": "Personal details" + "message": "جزئیات شخصی" }, "identification": { - "message": "Identification" + "message": "شناسایی" }, "contactInfo": { - "message": "Contact info" + "message": "اطلاعات مخاطب" }, "cardDetails": { - "message": "Card details" + "message": "جزئیات کارت" }, "cardBrandDetails": { "message": "$BRAND$ details", @@ -450,17 +450,17 @@ "message": "منطقی" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "کادر انتخاب" }, "cfTypeLinked": { "message": "پیوند شده", "description": "This describes a field that is 'linked' (related) to another field." }, "fieldType": { - "message": "Field type" + "message": "نوع فیلد" }, "fieldLabel": { - "message": "Field label" + "message": "برچسب فیلد" }, "remove": { "message": "حذف" @@ -473,7 +473,7 @@ "description": "This is the folder for uncategorized items" }, "selfOwnershipLabel": { - "message": "You", + "message": "شما", "description": "Used as a label to indicate that the user is the owner of an item." }, "addFolder": { @@ -496,7 +496,7 @@ } }, "newFolder": { - "message": "New folder" + "message": "پوشه جدید" }, "folderName": { "message": "Folder name" @@ -687,7 +687,7 @@ "message": "نام کامل" }, "address": { - "message": "Address" + "message": "نشانی" }, "address1": { "message": "نشانی ۱" @@ -732,7 +732,7 @@ "message": "مشاهده مورد" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "$TYPE$ جدید", "placeholders": { "type": { "content": "$1", @@ -741,7 +741,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "ویرایش $TYPE$", "placeholders": { "type": { "content": "$1", @@ -750,7 +750,7 @@ } }, "viewItemType": { - "message": "View $ITEMTYPE$", + "message": "مشاهده $ITEMTYPE$", "placeholders": { "itemtype": { "content": "$1", @@ -766,10 +766,10 @@ "message": "مورد" }, "itemDetails": { - "message": "Item details" + "message": "جزئیات مورد" }, "itemName": { - "message": "Item name" + "message": "نام مورد" }, "ex": { "message": "مثال.", @@ -795,7 +795,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "کپی موفق بود" }, "copyValue": { "message": "کپی مقدار", @@ -806,11 +806,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "کپی عبارت عبور", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "کلمه عبور کپی شد" }, "copyUsername": { "message": "کپی نام کاربری", @@ -829,7 +829,7 @@ "description": "Copy URI to clipboard" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "کپی $FIELD$", "placeholders": { "field": { "content": "$1", @@ -838,34 +838,34 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "کپی وب‌سایت" }, "copyNotes": { - "message": "Copy notes" + "message": "کپی یادداشت‌ها" }, "copyAddress": { - "message": "Copy address" + "message": "کپی نشانی" }, "copyPhone": { - "message": "Copy phone" + "message": "کپی تلفن" }, "copyEmail": { - "message": "Copy email" + "message": "کپی ایمیل" }, "copyCompany": { - "message": "Copy company" + "message": "کپی شرکت" }, "copySSN": { - "message": "Copy Social Security number" + "message": "کپی شماره کد ملی" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "کپی شماره گذرنامه" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "کپی شماره گواهینامه" }, "copyName": { - "message": "Copy name" + "message": "کپی نام" }, "me": { "message": "من" @@ -944,7 +944,7 @@ } }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "موارد به $ORGNAME$ منتقل شدند", "placeholders": { "orgname": { "content": "$1", @@ -953,7 +953,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "مورد به $ORGNAME$ منتقل شد", "placeholders": { "orgname": { "content": "$1", @@ -1019,16 +1019,16 @@ "message": "نشست ورود شما منقضی شده است." }, "restartRegistration": { - "message": "Restart registration" + "message": "ثبت‌نام را دوباره آغاز کنید" }, "expiredLink": { - "message": "Expired link" + "message": "پیوند منقضی شد" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "لطفاً ثبت نام را مجدداً شروع کنید یا دوباره وارد شوید." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "ممکن است قبلاً حساب کاربری داشته باشید" }, "logOutConfirmation": { "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" @@ -1046,7 +1046,7 @@ "message": "خیر" }, "location": { - "message": "Location" + "message": "موقعیت" }, "loginOrCreateNewAccount": { "message": "وارد شوید یا یک حساب کاربری بسازید تا به گاوصندوق امن‌تان دسترسی یابید." @@ -1058,28 +1058,28 @@ "message": "ورود به سیستم با دستگاه باید در تنظیمات برنامه‌ی Bitwarden تنظیم شود. به گزینه دیگری نیاز دارید؟" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "به گزینه دیگری نیاز دارید؟" }, "loginWithMasterPassword": { "message": "با کلمه عبور اصلی وارد شوید" }, "readingPasskeyLoading": { - "message": "Reading passkey..." + "message": "خواندن کلید عبور..." }, "readingPasskeyLoadingInfo": { - "message": "Keep this window open and follow prompts from your browser." + "message": "این پنجره را باز نگه دارید و دستورهای مرورگر خود را دنبال کنید." }, "useADifferentLogInMethod": { "message": "Use a different log in method" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "با کلید عبور وارد شوید" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "استفاده از ورود تک مرحله‌ای" }, "welcomeBack": { - "message": "Welcome back" + "message": "خوش آمدید" }, "invalidPasskeyPleaseTryAgain": { "message": "Invalid Passkey. Please try again." @@ -1163,13 +1163,13 @@ "message": "ایجاد حساب کاربری" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "در Bitwarden تازه وارد هستید؟" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "تنظیم کلمه عبور قوی" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "ایجاد حساب کاربری خود را با تنظیم کلمه عبور تکمیل کنید" }, "newAroundHere": { "message": "اینجا تازه واردی؟" @@ -1181,22 +1181,22 @@ "message": "ورود" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "وارد Bitwarden شوید" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "کد سامانه تأیید کننده را وارد نمایید" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "برای احراز هویت، کلید YubiKey خود را فشار دهید" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "پایان زمان احراز هویت" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "نشست احراز هویت منقضی شد. لطفاً فرایند ورود را دوباره شروع کنید." }, "verifyYourIdentity": { "message": "Verify your Identity" @@ -1736,25 +1736,25 @@ "message": "تاریخچه کلمه عبور" }, "generatorHistory": { - "message": "Generator history" + "message": "تاریخچه تولید کننده" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "پاک کردن تاریخچه تولید کننده" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "اگر ادامه دهید، تمام ورودی‌ها به‌طور دائمی از تاریخچه تولید کننده حذف خواهند شد. آیا مطمئن هستید که می‌خواهید ادامه دهید؟" }, "noPasswordsInList": { "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, "clearHistory": { - "message": "Clear history" + "message": "پاک کردن تاریخچه" }, "nothingToShow": { - "message": "Nothing to show" + "message": "چیزی برای نشان دادن موجود نیست" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "شما اخیراً چیزی تولید نکرده‌اید" }, "clear": { "message": "پاک کردن", @@ -1794,10 +1794,10 @@ "message": "لطفاً دوباره وارد شوید." }, "currentSession": { - "message": "Current session" + "message": "نشست کنونی" }, "requestPending": { - "message": "Request pending" + "message": "درخواست در حال انتظار است" }, "logBackInOthersToo": { "message": "لطفاً دوباره وارد شوید. اگر از سایر برنامه‌های Bitwarden استفاده می‌کنید، از سیستم خارج شوید و دوباره به آن‌ها وارد شوید." @@ -1961,11 +1961,11 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { - "message": "new item", + "message": "مورد جدید", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLoginLink": { - "message": "new login", + "message": "ورود جدید", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { @@ -2016,7 +2016,7 @@ "message": "خطا در رمزگشایی پرونده‌ی درون ریزی شده. کلید رمزگذاری شما با کلید رمزگذاری استفاده شده برای درون ریزی داده‌ها مطابقت ندارد." }, "destination": { - "message": "Destination" + "message": "مقصد" }, "learnAboutImportOptions": { "message": "درباره گزینه‌های برون ریزی خود بیاموزید" @@ -2201,16 +2201,16 @@ "message": "مدیریت" }, "manageCollection": { - "message": "Manage collection" + "message": "مدیریت مجموعه" }, "viewItems": { - "message": "View items" + "message": "مشاهده موارد" }, "viewItemsHidePass": { "message": "View items, hidden passwords" }, "editItems": { - "message": "Edit items" + "message": "ویرایش موارد" }, "editItemsHidePass": { "message": "Edit items, hidden passwords" @@ -2222,7 +2222,7 @@ "message": "لغو دسترسی" }, "revoke": { - "message": "Revoke" + "message": "لغو" }, "twoStepLoginProviderEnabled": { "message": "این ارائه دهنده ورود به سیستم دو مرحله ای در حساب شما فعال است." @@ -2237,7 +2237,7 @@ "message": "," }, "twoStepAuthenticatorInstructionInfix2": { - "message": "or" + "message": "یا" }, "twoStepAuthenticatorInstructionSuffix": { "message": "." @@ -2255,10 +2255,10 @@ "message": "You are leaving Bitwarden and launching an external website in a new window." }, "twoStepContinueToBitwardenUrlTitle": { - "message": "Continue to bitwarden.com?" + "message": "به bitwarden.com ادامه می‌دهید؟" }, "twoStepContinueToBitwardenUrlDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website." + "message": "احراز هویت کننده Bitwarden به شما امکان می‌دهد کلیدهای احراز هویت را ذخیره کرده و کدهای TOTP را برای فرآیندهای تأیید دومرحله‌ای تولید کنید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید." }, "twoStepAuthenticatorScanCodeV2": { "message": "Scan the QR code below with your authenticator app or enter the key." @@ -2270,7 +2270,7 @@ "message": "کلید" }, "twoStepAuthenticatorEnterCodeV2": { - "message": "Verification code" + "message": "کد تأیید" }, "twoStepAuthenticatorReaddDesc": { "message": "در صورت نیاز به افزودن آن به دستگاه دیگر، کد QR (یا کلید) مورد نیاز برنامه احراز هویت شما در زیر آمده است." @@ -2351,7 +2351,7 @@ "message": "اطلاعات برنامه Bitwarden را از پنل مدیریت Duo خود وارد کنید." }, "twoFactorDuoClientId": { - "message": "Client Id" + "message": "شناسه کاربر" }, "twoFactorDuoClientSecret": { "message": "Client Secret" @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ در حال استفاده از SSO با یک سرور کلید خود میزبان است. برای ورود اعضای این سازمان دیگر نیازی به کلمه عبور اصلی نیست.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "ترک سازمان" diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 83efcc197fc..ccbcf4f7cc3 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Virheellinen todennuskoodi" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ käyttää kertakirjautumista itse ylläpidetyllä avainpalvelimella. Organisaation jäsenet eivät enää tarvitse pääsalasanaa kirjautumiseen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Eroa organisaatiosta" diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 15e0793d6ce..97e22e18045 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Maling verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ay gumagamit ng SSO na may sariling-hosted na key server. Walang kinakailangang master password para mag-log in para sa mga miyembro ng organisasyon na ito.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Umalis sa organisasyon" diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 39fe5b3c1cf..d881ecfefd3 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Code de vérification invalide" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ utilise le SSO avec un serveur de clés auto-hébergé. Un mot de passe principal n'est plus nécessaire aux membres de cette organisation pour se connecter.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Un mot de passe maître n'est plus requis pour les membres de l'organisation suivante. Veuillez confirmer le domaine ci-dessous avec l'administrateur de votre organisation." + }, + "keyConnectorDomain": { + "message": "Domaine Key Connector" }, "leaveOrganization": { "message": "Quitter l'organisation" diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index eae36865876..707f9752ab6 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 286276f96e5..6dd2d9243b1 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "קוד אימות שגוי" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ משתמש/ת ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית כבר לא נדרשת כדי להיכנס עבור חברים של ארגון זה.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "עזוב ארגון" diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index dd2073a924f..22a31e5df70 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 4f1c4c981b2..3c37dc9cedf 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Nevažeći kôd za provjeru" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ koristi jedinstvenu prijavu SSO s vlastitim poslužiteljem. Članovima organizacije glavna lozinka više nije potrebna.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Napusti organizaciju" diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 12fd95bbbaa..33ea6c3d9cd 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Érvénytelen ellenőrző kód" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ jelenleg saját tárolású aláíráskulcsú SSO szervert használ. A mesterjelszó a továbbiakban nem szükséges a szervezeti tagsági bejelentkezéshez.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A következő szervezet tagjai számára már nincs szükség mesterjelszóra. Erősítsük meg az alábbi tartományt a szervezet adminisztrátorával." + }, + "keyConnectorDomain": { + "message": "Key Connector tartomány" }, "leaveOrganization": { "message": "Szervezet elhagyása" diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 3706fb73f63..379a6424333 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Tinggalkan Organisasi" diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index a30c1f81f93..8019ac7a750 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Codice di verifica non valido" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ sta usando SSO con un server self-hosted. Una password principale non è più necessaria per accedere per i membri di questa organizzazione.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lascia organizzazione" diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 1bb8bf364b0..2de545cd27a 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "認証コードが間違っています" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ は自己ホストの鍵サーバで SSO を使用しています。この組織のメンバーのログインにマスターパスワードは必要ありません。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "組織から脱退する" diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 72e9066806c..863f8d11dea 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 38740c78cba..27a160a9a3c 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 2758b022b64..98c317d377f 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index ee089584adf..2340ae849fa 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "조직 나가기" diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 61ff8651c72..27818e4e3b9 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Nederīgs apliecinājuma kods" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ izmanto vienoto pieteikšanos ar pašmitinātu atslēgu serveri. Tās dalībniekiem vairs nav nepieciešama galvenā parole, lai pieteiktos.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Galvenā parole vairs nav nepieciešama turpmāk minētās apvienības dalībniekiem. Lūgums saskaņot zemāk esošo domēnu ar savas apvienības pārvaldītāju." + }, + "keyConnectorDomain": { + "message": "Key Connector domēns" }, "leaveOrganization": { "message": "Pamest apvienību" diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index dd4ca0b94cf..54a837b83e1 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 38740c78cba..27a160a9a3c 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 38740c78cba..27a160a9a3c 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 93df01f3440..b2068d1d939 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ugyldig bekreftelseskode" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruker SSO med en selvdrevet nøkkelserver. Et hovedpassord er ikke lenger nødvendig for å logge inn for medlemmer av denne organisasjonen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlat organisasjonen" diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 215ebaf1849..c8c0c90dcde 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 9d192639377..d92b9707fc9 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ongeldige verificatiecode" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruikt SSO met een zelf gehoste sleutelserver. Leden van deze organisatie kunnen inloggen zonder hoofdwachtwoord.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Voor leden van de volgende organisatie is een hoofdwachtwoord niet langer nodig. Bevestig het domein hieronder met de beheerder van je organisatie." + }, + "keyConnectorDomain": { + "message": "Key Connector-domein" }, "leaveOrganization": { "message": "Organisatie verlaten" diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 3c03b2e7cb2..619c1776cb6 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 38740c78cba..27a160a9a3c 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index ab471e892ef..de9caf9bbce 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Kod weryfikacyjny jest nieprawidłowy" }, - "convertOrganizationEncryptionDesc": { - "message": "Organizacja $ORGANIZATION$ używa jednokrotnego logowania SSO z własnym serwerem kluczy. Użytkownicy nie muszą logować się za pomocą hasła głównego.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hasło główne nie jest już wymagane dla członków następującej organizacji. Proszę potwierdzić poniższą domenę u administratora organizacji." + }, + "keyConnectorDomain": { + "message": "Domena Key Connector'a" }, "leaveOrganization": { "message": "Opuść organizację" diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index b3f6dd37a8c..04403ade2d9 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO com um servidor de chaves auto-hospedado. Não é mais necessária uma senha mestra para os membros desta organização entrarem.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Sair da Organização" diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 5f1e02c82e3..dc66923f629 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, - "convertOrganizationEncryptionDesc": { - "message": "A $ORGANIZATION$ está a utilizar o SSO com um servidor de chaves auto-hospedado. Já não é necessária uma palavra-passe mestra para iniciar sessão para os membros desta organização.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Já não é necessária uma palavra-passe mestra para os membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." + }, + "keyConnectorDomain": { + "message": "Domínio do Key Connector" }, "leaveOrganization": { "message": "Sair da organização" diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 64eb05dad22..632733642b1 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Cod de verificare nevalid" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ folosește SSO cu un server de chei auto-găzduit. Membrii acestei organizații nu mai au nevoie de o parolă principală pentru autentificare.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Părăsire organizație" diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index aaccc3b2145..c30371f7d86 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -141,13 +141,13 @@ "message": "Эти пользователи входят в приложения со слабыми, скомпрометированными или повторно используемыми паролями." }, "atRiskMembersDescriptionNone": { - "message": "These are no members logging into applications with weak, exposed, or reused passwords." + "message": "Нет пользователей, входящих в приложения со слабыми, скомпрометированными или повторно используемыми паролями." }, "atRiskApplicationsDescription": { "message": "Эти приложения имеют слабые, скомпрометированные или повторно используемые пароли." }, "atRiskApplicationsDescriptionNone": { - "message": "These are no applications with weak, exposed, or reused passwords." + "message": "Нет приложений со слабыми, скомпрометированными или повторно используемыми паролями." }, "atRiskMembersDescriptionWithApp": { "message": "Эти пользователи входят в $APPNAME$ со слабыми, скомпрометированными или повторно используемыми паролями.", @@ -159,7 +159,7 @@ } }, "atRiskMembersDescriptionWithAppNone": { - "message": "There are no at risk members for $APPNAME$.", + "message": "Нет пользователей, подверженных риску для $APPNAME$.", "placeholders": { "appname": { "content": "$1", @@ -235,7 +235,7 @@ "message": "Данные для авторизации" }, "personalDetails": { - "message": "Личные данные" + "message": "Личная информация" }, "identification": { "message": "Идентификация" @@ -333,7 +333,7 @@ "message": "Код безопасности / CVV" }, "identityName": { - "message": "Название личности" + "message": "Название личной информации" }, "company": { "message": "Компания" @@ -610,7 +610,7 @@ "description": "Search Card type" }, "searchIdentity": { - "message": "Поиск личностей", + "message": "Поиск личной информации", "description": "Search Identity type" }, "searchSecureNote": { @@ -2096,7 +2096,7 @@ "message": "Правила домена" }, "domainRulesDesc": { - "message": "Если у вас есть тот же логин на нескольких разных доменах сайта, вы можете отметить сайт как \"эквивалентный\". \"Глобальные\" - это домены, созданные для вас Bitwarden." + "message": "Если у вас есть такой же логин на нескольких разных доменах сайта, вы можете отметить сайт как \"эквивалентный\". \"Глобальные\" домены это домены, созданные Bitwarden для вас." }, "globalEqDomains": { "message": "Глобальные эквивалентные домены" @@ -4549,7 +4549,7 @@ "message": "После обновления ключа шифрования необходимо выйти из всех приложений Bitwarden, которые вы используете (например, из мобильного приложения или расширения браузера). Если этого не сделать, могут повредиться данные (так как при выходе и последующем входе загружается ваш новый ключ шифрования). Мы попытаемся автоматически осуществить завершение ваших сессий, однако это может произойти с задержкой." }, "updateEncryptionKeyAccountExportWarning": { - "message": "Any account restricted exports you have saved will become invalid." + "message": "Все сохраненные экспорты, затронутые ограничениями аккаунта, будут аннулированы." }, "subscription": { "message": "Подписка" @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Неверный код подтверждения" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ использует SSO с собственным сервером ключей. Для авторизации пользователям этой организации больше не требуется мастер-пароль.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Мастер-пароль больше не требуется для членов следующей организации. Пожалуйста, подтвердите указанный ниже домен у администратора вашей организации." + }, + "keyConnectorDomain": { + "message": "Домен соединителя ключей" }, "leaveOrganization": { "message": "Покинуть организацию" @@ -10584,7 +10581,7 @@ "message": "Упрощение создания аккаунтов" }, "newIdentityNudgeBody": { - "message": "С помощью личностей можно быстро заполнять длинные регистрационные или контактные формы." + "message": "С помощью личной информации можно быстро заполнять длинные регистрационные или контактные формы." }, "newNoteNudgeTitle": { "message": "Храните ваши конфиденциальные данные в безопасности" diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 2d270a54731..f75fd53c209 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 6bbf765ba78..e785bd3749b 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -159,7 +159,7 @@ } }, "atRiskMembersDescriptionWithAppNone": { - "message": "There are no at risk members for $APPNAME$.", + "message": "Pre $APPNAME$ neexistujú žiadni ohrození členovia.", "placeholders": { "appname": { "content": "$1", @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Neplatný verifikačný kód" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používa SSO s vlastným kľúčovým serverom. Na prihlásenie členov tejto organizácie už nie je potrebné hlavné heslo.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Opustiť organizáciu" @@ -10612,6 +10609,6 @@ "message": "Platba prostredníctvom bankového účtu je dostupná len pre zákazníkov v Spojených Štátoch. Budete musieť overiť svoj bankový účet. V priebehu nasledujúcich 1-2 pracovných dní vykonáme mikro vklad. Na overenie bankového účtu zadajte kód popisu výpisu z tohto vkladu na fakturačnej stránke poskytovateľa. Neoverenie bankového účtu bude mať za následok neuskutočnenie platby a pozastavenie vášho predplatného." }, "clickPayWithPayPal": { - "message": "Please click the Pay with PayPal button to add your payment method." + "message": "Pre pridanie platobnej metódy kliknite prosím na Zaplatiť cez PayPal." } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 06c5339ad98..5a43561bf5a 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 2ca7e72e227..e54b6d148ca 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -99,7 +99,7 @@ "message": "Означите апликацију као критичну" }, "applicationsMarkedAsCriticalSuccess": { - "message": "Applications marked as critical" + "message": "Апликације означене као критичне" }, "application": { "message": "Апликација" @@ -141,13 +141,13 @@ "message": "Ови чланови се пријављују у апликације са слабим, откривеним или поново коришћеним лозинкама." }, "atRiskMembersDescriptionNone": { - "message": "These are no members logging into applications with weak, exposed, or reused passwords." + "message": "Нема чланова пријављена у апликације са слабим, откривеним или поново коришћеним лозинкама." }, "atRiskApplicationsDescription": { "message": "Ове апликације имају слабу, проваљену или често коришћену лозинку." }, "atRiskApplicationsDescriptionNone": { - "message": "These are no applications with weak, exposed, or reused passwords." + "message": "Ове апликације немају слабу, проваљену или често коришћену лозинку." }, "atRiskMembersDescriptionWithApp": { "message": "Ови чланови се пријављују у $APPNAME$ са слабим, откривеним или поново коришћеним лозинкама.", @@ -4549,7 +4549,7 @@ "message": "Након ажурирања кључа за шифровање, мораћете да се одјавите и вратите у све Bitwarden апликације које тренутно користите (као што су мобилна апликација или додаци прегледача). Ако се не одјавите и поново пријавите (чиме се преузима ваш нови кључ за шифровање), може доћи до оштећења података. Покушаћемо аутоматски да се одјавимо, али може доћи до одлагања." }, "updateEncryptionKeyAccountExportWarning": { - "message": "Any account restricted exports you have saved will become invalid." + "message": "Сваки рачун са ограничен извоз који сте сачували постаће неважећи." }, "subscription": { "message": "Претплата" @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Неисправан верификациони код" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Главна лозинка више није потребна за чланове следеће организације. Молимо потврдите домен са администратором организације." + }, + "keyConnectorDomain": { + "message": "Домен конектора кључа" }, "leaveOrganization": { "message": "Напусти организацију" @@ -9429,19 +9426,19 @@ "message": "Управљајте наплатом из Provider Portal" }, "continueSettingUp": { - "message": "Continue setting up Bitwarden" + "message": "Наставити са подешавањем Bitwarden-а" }, "continueSettingUpFreeTrial": { "message": "Наставите са подешавањем бесплатне пробне верзије Bitwarden-а" }, "continueSettingUpPasswordManager": { - "message": "Continue setting up Bitwarden Password Manager" + "message": "Наставите са подешавањем Bitwarden менаџер лозинки" }, "continueSettingUpFreeTrialPasswordManager": { "message": "Наставите са подешавањем бесплатне пробне верзије Bitwarden менаџер лозинки" }, "continueSettingUpSecretsManager": { - "message": "Continue setting up Bitwarden Secrets Manager" + "message": "Наставите са подешавањем Bitwarden Secrets Manager" }, "continueSettingUpFreeTrialSecretsManager": { "message": "Наставите са подешавањем бесплатне пробне верзије Bitwarden Secrets Manager" @@ -10560,17 +10557,17 @@ "message": "Уштедите време са ауто-пуњењем" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "Укључите", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Веб сајт", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "тако да се ова пријава појављује као предлог за ауто-пуњење.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -10596,12 +10593,12 @@ "message": "Лак SSH приступ" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "Чувајте кључеве и повежите се са SSH агент за брзу, шифровану аутентификацију.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "Сазнајте више о SSH агенту", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, @@ -10612,6 +10609,6 @@ "message": "Плаћање са банковним рачуном доступно је само купцима у Сједињеним Државама. Од вас ће бити потребно да проверите свој банковни рачун. Направит ћемо микро депозит у наредних 1-2 радна дана. Унесите кôд за дескриптор изјаве са овог депозита на претплатничкој страници провајдера да бисте потврдили банковни рачун. Неуспех у верификацији банковног рачуна резултираће пропуштеним плаћањем и претплатом је суспендовано." }, "clickPayWithPayPal": { - "message": "Please click the Pay with PayPal button to add your payment method." + "message": "Кликните на Pay with PayPal да бисте додали начин лпаћања." } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 6b10fd30018..95b9348aa04 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 1ca059f95ec..3bbfc4577ea 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ogiltig verifieringskod" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lämna organisation" diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 38740c78cba..27a160a9a3c 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 393442e42e5..7651ae5bc06 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index c59f33bba45..7e6f3e6ab4e 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Geçersiz doğrulama kodu" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kendi barındırdığı bir anahtar sunucusuyla SSO kullanıyor. Bu kuruluşun üyelerinin artık ana parola kullanması gerekmiyor.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector alan adı" }, "leaveOrganization": { "message": "Kuruluştan ayrıl" diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 288e8e840a0..a899bde3d04 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Недійсний код підтвердження" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ використовує SSO з власним сервером ключів. Головний пароль для учасників цієї організації більше не вимагається.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Покинути організацію" diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index cee1bf8ebbd..78a536991a7 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 9805eaac5fd..eb186f11360 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -1870,7 +1870,7 @@ "message": "取消会话授权" }, "deauthorizeSessionsDesc": { - "message": "您是否担心自己的账户在其他设备上登录过?请按照以下步骤取消对之前使用过的所有计算机或设备的授权。如果您以前使用过公共计算机或不小心曾将密码保存在不属于您的设备上,则建议执行此安全步骤。此步骤还将清除所有以前记住的两步登录会话。" + "message": "您是否担心自己的账户在其他设备上登录过?继续下面的操作以取消对之前使用过的所有计算机或设备的授权。如果您以前使用过公共计算机或不小心曾将密码保存在不属于您的设备上,则建议执行此安全步骤。此步骤还将清除所有以前记住的两步登录会话。" }, "deauthorizeSessionsWarning": { "message": "继续操作还将使您退出当前会话,并要求您重新登录。如果有设置两步登录,也需要重新验证。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -1885,10 +1885,10 @@ "message": "启用新设备登录保护" }, "turnOffNewDeviceLoginProtectionModalDesc": { - "message": "继续下面的操作以停用 Bitwarden 在您从新设备登录时发送验证电子邮件的功能。" + "message": "继续下面的操作以停用从新设备登录 Bitwarden 时发送验证电子邮件的功能。" }, "turnOnNewDeviceLoginProtectionModalDesc": { - "message": "继续下面的操作以启用 Bitwarden 在您从新设备登录时发送验证电子邮件的功能。" + "message": "继续下面的操作以启用从新设备登录 Bitwarden 时发送验证电子邮件的功能。" }, "turnOffNewDeviceLoginProtectionWarning": { "message": "停用新设备登录保护后,任何拥有您的主密码的人都可以从任何设备访问您的账户。要在没有验证电子邮件的情况下保护您的账户,请设置两步登录。" @@ -1918,10 +1918,10 @@ "message": "密码库被提供商访问。" }, "purgeVaultDesc": { - "message": "接下来的操作会删除密码库中的所有项目和文件夹。属于组织的共享项目将不会被删除。" + "message": "继续下面的操作以删除密码库中的所有项目和文件夹。属于组织的共享项目将不会被删除。" }, "purgeOrgVaultDesc": { - "message": "接下来的操作会删除组织密码库中的所有项目。" + "message": "继续下面的操作以删除组织密码库中的所有项目。" }, "purgeVaultWarning": { "message": "清空密码库是永久性操作,无法撤销!" @@ -1933,7 +1933,7 @@ "message": "删除账户" }, "deleteAccountDesc": { - "message": "接下来的操作会删除您的账户和所有密码库数据。" + "message": "继续下面的操作以删除您的账户和所有密码库数据。" }, "deleteAccountWarning": { "message": "删除账户是永久性操作,无法撤销!" @@ -4549,7 +4549,7 @@ "message": "更新加密密钥后,您需要注销所有当前使用的 Bitwarden 应用程序(例如移动 App 或浏览器扩展)然后重新登录。注销或者重新登录(这将下载新的加密密钥)失败可能会导致数据损坏。我们会尝试自动为您注销,但可能会有所延迟。" }, "updateEncryptionKeyAccountExportWarning": { - "message": "您已保存的所有账户限制的导出文件将失效。" + "message": "所有您已保存的账户限制的导出文件将失效。" }, "subscription": { "message": "订阅" @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "无效的验证码" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自托管密钥服务器 SSO。这个组织的成员登录时将不再需要主密码。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "以下组织的成员不再需要主密码。请与您的组织管理员确认下面的域名。" + }, + "keyConnectorDomain": { + "message": "Key Connector 域名" }, "leaveOrganization": { "message": "退出组织" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 57dd4c8631f..2f5567308d2 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "無效的驗證碼" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自我裝載金鑰伺服器 SSO。此組織的成員登入時將不再需要主密碼。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "離開組織" From eb3e14b02211210daf1c77c7d26b52ff7dfc7527 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 12:29:05 +0200 Subject: [PATCH 10/41] Autosync the updated translations (#14891) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 25 +- apps/desktop/src/locales/ar/messages.json | 25 +- apps/desktop/src/locales/az/messages.json | 25 +- apps/desktop/src/locales/be/messages.json | 25 +- apps/desktop/src/locales/bg/messages.json | 25 +- apps/desktop/src/locales/bn/messages.json | 25 +- apps/desktop/src/locales/bs/messages.json | 25 +- apps/desktop/src/locales/ca/messages.json | 25 +- apps/desktop/src/locales/cs/messages.json | 25 +- apps/desktop/src/locales/cy/messages.json | 25 +- apps/desktop/src/locales/da/messages.json | 25 +- apps/desktop/src/locales/de/messages.json | 25 +- apps/desktop/src/locales/el/messages.json | 25 +- apps/desktop/src/locales/en_GB/messages.json | 25 +- apps/desktop/src/locales/en_IN/messages.json | 25 +- apps/desktop/src/locales/eo/messages.json | 25 +- apps/desktop/src/locales/es/messages.json | 87 ++- apps/desktop/src/locales/et/messages.json | 25 +- apps/desktop/src/locales/eu/messages.json | 25 +- apps/desktop/src/locales/fa/messages.json | 781 ++++++++++--------- apps/desktop/src/locales/fi/messages.json | 69 +- apps/desktop/src/locales/fil/messages.json | 25 +- apps/desktop/src/locales/fr/messages.json | 25 +- apps/desktop/src/locales/gl/messages.json | 25 +- apps/desktop/src/locales/he/messages.json | 25 +- apps/desktop/src/locales/hi/messages.json | 25 +- apps/desktop/src/locales/hr/messages.json | 25 +- apps/desktop/src/locales/hu/messages.json | 25 +- apps/desktop/src/locales/id/messages.json | 25 +- apps/desktop/src/locales/it/messages.json | 25 +- apps/desktop/src/locales/ja/messages.json | 25 +- apps/desktop/src/locales/ka/messages.json | 25 +- apps/desktop/src/locales/km/messages.json | 25 +- apps/desktop/src/locales/kn/messages.json | 25 +- apps/desktop/src/locales/ko/messages.json | 25 +- apps/desktop/src/locales/lt/messages.json | 25 +- apps/desktop/src/locales/lv/messages.json | 25 +- apps/desktop/src/locales/me/messages.json | 25 +- apps/desktop/src/locales/ml/messages.json | 25 +- apps/desktop/src/locales/mr/messages.json | 25 +- apps/desktop/src/locales/my/messages.json | 25 +- apps/desktop/src/locales/nb/messages.json | 25 +- apps/desktop/src/locales/ne/messages.json | 25 +- apps/desktop/src/locales/nl/messages.json | 25 +- apps/desktop/src/locales/nn/messages.json | 25 +- apps/desktop/src/locales/or/messages.json | 25 +- apps/desktop/src/locales/pl/messages.json | 25 +- apps/desktop/src/locales/pt_BR/messages.json | 25 +- apps/desktop/src/locales/pt_PT/messages.json | 25 +- apps/desktop/src/locales/ro/messages.json | 25 +- apps/desktop/src/locales/ru/messages.json | 29 +- apps/desktop/src/locales/si/messages.json | 25 +- apps/desktop/src/locales/sk/messages.json | 25 +- apps/desktop/src/locales/sl/messages.json | 25 +- apps/desktop/src/locales/sr/messages.json | 71 +- apps/desktop/src/locales/sv/messages.json | 25 +- apps/desktop/src/locales/te/messages.json | 25 +- apps/desktop/src/locales/th/messages.json | 25 +- apps/desktop/src/locales/tr/messages.json | 25 +- apps/desktop/src/locales/uk/messages.json | 25 +- apps/desktop/src/locales/vi/messages.json | 167 ++-- apps/desktop/src/locales/zh_CN/messages.json | 29 +- apps/desktop/src/locales/zh_TW/messages.json | 25 +- 63 files changed, 1600 insertions(+), 1033 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 479f8f47c1f..28cff8cae13 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hoofwagwoord is verwyder" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruik SSO met ’n sleutelbediener op ’n eie gasheer. ’n Hoofwagwoord word nie meer vereis vir aantekening vir lede van hierdie organisasie nie.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Verlaat organisasie" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 510c1aa918a..d9dfb7cdff5 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "تمت إزالة كلمة المرور الرئيسية." }, - "convertOrganizationEncryptionDesc": { - "message": "يستخدم $ORGANIZATION$ SSO مع خادم مفتاح الاستضافة الذاتية. لم تعد هناك حاجة إلى كلمة مرور رئيسية لتسجيل الدخول لأعضاء هذه المؤسسة.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "مغادرة المؤسسة" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 459458bdb2a..5c1f49cbfff 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Ana parol silindi." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$, self-hosted açar serveri ilə SSO istifadə edir. Bu təşkilatın üzvlərinin giriş etməsi üçün artıq ana parol tələb edilməyəcək.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Aşağıdakı təşkilatların üzvləri üçün artıq ana parol tələb olunmur. Lütfən aşağıdakı domeni təşkilatınızın inzibatçısı ilə təsdiqləyin." + }, + "organizationName": { + "message": "Təşkilat adı" + }, + "keyConnectorDomain": { + "message": "Key Connector domeni" }, "leaveOrganization": { "message": "Təşkilatı tərk et" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Riskli parolları dəyişdir" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Daşı" }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 4e2441ac168..9284f683e58 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Асноўны пароль выдалены." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ выкарыстоўвае SSO з уласным серверам ключоў. Асноўны пароль для ўдзельнікаў гэтай арганізацыі больш не патрабуецца.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Выйсці з арганізацыі" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 6300ed3391f..18a1d8cf2f0 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Главната парола е премахната." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ използва еднократно удостоверяване със собствен сървър за ключове. Членовете на тази организация вече нямат нужда от главна парола за вписване.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "За членовете на следната организация вече не се изисква главна парола. Потвърдете домейна по-долу с администратора на организацията си." + }, + "organizationName": { + "message": "Име на организацията" + }, + "keyConnectorDomain": { + "message": "Домейн на конектора за ключове" }, "leaveOrganization": { "message": "Напускане на организацията" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Промяна на парола в риск" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Преместване" }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 3ca32f51395..b3a2b6e21ee 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 4e77b51467b..89015368c11 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 2f13de7c33b..5550e2d0c62 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "S'ha suprimit la contrasenya mestra." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ està utilitzant SSO amb un servidor autoallotjat de claus. Ja no es requereix una contrasenya mestra d'inici de sessió per als membres d'aquesta organització.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandona l'organització" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 49cfc8823b7..b6c3edd783a 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hlavní heslo bylo odebráno" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používá SSO s vlastním serverem s klíči. Hlavní heslo pro členy této organizace již není vyžadováno.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavní heslo již není vyžadováno pro členy následující organizace. Potvrďte níže uvedenou doménu u správce Vaší organizace." + }, + "organizationName": { + "message": "Název organizace" + }, + "keyConnectorDomain": { + "message": "Doména Key Connectoru" }, "leaveOrganization": { "message": "Opustit organizaci" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Změnit ohrožené heslo" }, + "cannotRemoveViewOnlyCollections": { + "message": "Nemůžete odebrat kolekce s oprávněními jen pro zobrazení: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Přesunout" }, diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 0031d2de121..28ed661423d 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index c92f0caf2d1..f547eea69a0 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hovedadgangskode fjernet." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruger SSO med en selv-hostet nøgleserver. Organisationsmedlemmer behøver ikke længere hovedadgangskode for at logge ind.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlad organisation" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index da1272ef300..de39eba6f99 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master-Passwort entfernt" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ verwendet SSO mit einem selbst gehosteten Schlüsselserver. Ein Master-Passwort ist nicht mehr erforderlich, damit sich Mitglieder dieser Organisation anmelden können.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Für Mitglieder der folgenden Organisation ist kein Master-Passwort mehr erforderlich. Bitte bestätige die folgende Domain bei deinem Organisations-Administrator." + }, + "organizationName": { + "message": "Name der Organisation" + }, + "keyConnectorDomain": { + "message": "Key Connector-Domain" }, "leaveOrganization": { "message": "Organisation verlassen" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Gefährdetes Passwort ändern" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Verschieben" }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 1f4462ce4b5..3133488f805 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Ο κύριος κωδικός αφαιρέθηκε" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ χρησιμοποιεί SSO με έναν αυτοεξυπηρετητή κλειδιών. Ένας κύριος κωδικός πρόσβασης δεν απαιτείται πλέον για να συνδεθείτε για τα μέλη αυτού του οργανισμού.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Αποχώρηση από τον οργανισμό" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Μετακίνηση" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 77e4222ac1f..cf4d0986e1f 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "organizationName": { + "message": "Organisation name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organisation" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 109ace48efa..d82cb566a98 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "organizationName": { + "message": "Organisation name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 51c350831a2..8e8cc09341f 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 3023fdd0359..154bb327689 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -232,7 +232,7 @@ "message": "Habilitar agente SSH" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "Activa el agente SSH para firmar peticiones SSH directamente desde tu caja fuerte de Bitwarden." }, "enableSshAgentHelp": { "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." @@ -434,28 +434,28 @@ "message": "Website added" }, "addWebsite": { - "message": "Add website" + "message": "Añadir página web" }, "deleteWebsite": { - "message": "Delete website" + "message": "Eliminar página web" }, "owner": { - "message": "Owner" + "message": "Propietario" }, "addField": { "message": "Añadir campo" }, "editField": { - "message": "Edit field" + "message": "Editar campo" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "¿Estás seguro de que quieres eliminar permanentemente este adjunto?" }, "fieldType": { - "message": "Field type" + "message": "Tipo de campo" }, "fieldLabel": { - "message": "Field label" + "message": "Etiqueta del campo" }, "add": { "message": "Añadir" @@ -931,11 +931,11 @@ "message": "No volver a preguntar en este dispositivo durante 30 días" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "Selecciona otro método", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "Usar tu código de recuperación" }, "insertU2f": { "message": "Inserta tu llave de seguridad en el puerto USB de tu equipo. Si tiene un botón, púlsalo." @@ -1028,7 +1028,7 @@ "message": "The authentication session timed out. Please restart the login process." }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL del servidor autoalojado", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -2001,10 +2001,10 @@ "message": "Free organizations cannot use attachments" }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 campo necesita tu atención." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ campos necesitan tu atención.", "placeholders": { "count": { "content": "$1", @@ -2013,10 +2013,10 @@ } }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Tarjeta expirada" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "Si la has renovado, actualiza la información de la tarjeta" }, "verificationRequired": { "message": "Verificación requerida", @@ -2167,10 +2167,10 @@ "message": "La biometría del navegador requiere habilitar primero la biometría de escritorio en los ajustes." }, "biometricsManualSetupTitle": { - "message": "Automatic setup not available" + "message": "Configuración automática no disponible" }, "biometricsManualSetupDesc": { - "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + "message": "Debido al método de instalación, el soporte biométrico no pudo activarse automáticamente. ¿Quieres abrir la documentación sobre cómo hacerlo manualmente?" }, "personalOwnershipSubmitError": { "message": "Debido a una política de organización, tiene restringido el guardar elementos a su caja fuerte personal. Cambie la configuración de propietario a organización y elija entre las colecciones disponibles." @@ -2188,13 +2188,13 @@ "message": "Una política organizacional ha bloqueado la importación de elementos a su caja fuerte personal." }, "personalDetails": { - "message": "Personal details" + "message": "Datos personales" }, "identification": { - "message": "Identification" + "message": "Identificación" }, "contactInfo": { - "message": "Contact information" + "message": "Información de contacto" }, "allSends": { "message": "Todos los Send", @@ -2363,7 +2363,7 @@ "message": "Leer clave de seguridad" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "Esperando interacción de la clave de seguridad..." }, "hideEmail": { "message": "Ocultar mi dirección de correo electrónico a los destinatarios." @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Contraseña maestra eliminada." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO con un servidor de claves autoalojado. Los miembros de esta organización ya no necesitarán una contraseña maestra para iniciar sesión.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Nombre de la organización" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandonar organización" @@ -2946,7 +2946,7 @@ "message": "Are you trying to access your account?" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "Intento de acceso de $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -3016,7 +3016,7 @@ "message": "Inicio de sesión solicitado" }, "accountAccessRequested": { - "message": "Account access requested" + "message": "Acceso a la cuenta solicitado" }, "creatingAccountOn": { "message": "Creando una cuenta en" @@ -3161,10 +3161,10 @@ "message": "Trust organization" }, "trust": { - "message": "Trust" + "message": "Confiar" }, "doNotTrust": { - "message": "Do not trust" + "message": "No confiar" }, "organizationNotTrusted": { "message": "Organization is not trusted" @@ -3623,7 +3623,7 @@ "message": "Login credentials" }, "additionalOptions": { - "message": "Additional options" + "message": "Opciones adicionales" }, "itemHistory": { "message": "Item history" @@ -3659,10 +3659,10 @@ "message": "authenticate to a server" }, "sshActionSign": { - "message": "sign a message" + "message": "firmar un mensaje" }, "sshActionGitSign": { - "message": "sign a git commit" + "message": "firmar una confirmación git" }, "unknownApplication": { "message": "Una aplicación" @@ -3683,7 +3683,7 @@ "message": "File saved to device. Manage from your device downloads." }, "allowScreenshots": { - "message": "Allow screen capture" + "message": "Permitir captura de pantalla" }, "allowScreenshotsDesc": { "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." @@ -3703,11 +3703,20 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Mover" }, "newFolder": { - "message": "New folder" + "message": "Nueva carpeta" }, "folderName": { "message": "Folder Name" @@ -3724,7 +3733,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Página web", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 922ab7e36a6..1cc526ecfba 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Ülemparool on eemaldatud." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kasutab SSO-d koos enda majutatud võtmeserveriga. Selle organisatsiooni liikmed ei pea sisselogimisel enam ülemparooli kasutama.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lahku organisatsioonist" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 3bc76d8427c..ff8d91873b7 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Pasahitz nagusia ezabatua." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ostatatze propioko gako-zerbitzari batekin SSO erabiltzen ari da. Dagoeneko ez da pasahitz nagusirik behar erakunde honetako kideentzat saioa hasteko.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Utzi erakundea" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 4bcbbd3ac33..e0050ea092a 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -27,7 +27,7 @@ "message": "یادداشت امن" }, "typeSshKey": { - "message": "SSH key" + "message": "کلید SSH" }, "folders": { "message": "پوشه‌ها" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "خوش آمدید" }, "moveToOrgDesc": { "message": "سازمانی را انتخاب کنید که می‌خواهید این مورد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت مورد را به آن سازمان منتقل می‌کند. پس از انتقال این مورد، دیگر مالک مستقیم آن نخواهید بود." @@ -181,16 +181,16 @@ "message": "نشانی" }, "sshPrivateKey": { - "message": "Private key" + "message": "کلید خصوصی" }, "sshPublicKey": { - "message": "Public key" + "message": "کلید عمومی" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "اثر انگشت" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "نوع کلید" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -205,55 +205,55 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "یک کلید SSH جدید ایجاد شد" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "کلمه عبور وارد شده اشتباه است." }, "importSshKey": { - "message": "Import" + "message": "درون ریزی" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "تأیید کلمه عبور" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "کلمه عبور کلید SSH را وارد کنید." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "کلمه عبور را وارد کنید" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "لطفاً برای تأیید درخواست کلید SSH، گاوصندوق خود را باز کنید." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "درخواست کلید SSH منقضی شد." }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "عامل SSH را فعال کنید" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "عامل SSH را فعال کنید تا درخواست‌های SSH را مستقیماً از گاوصندوق Bitwarden خود امضا کنید." }, "enableSshAgentHelp": { - "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + "message": "عامل SSH یک سرویس مخصوص توسعه‌دهندگان است که به شما امکان می‌دهد درخواست‌های SSH را مستقیماً از گاوصندوق Bitwarden خود امضا کنید." }, "sshAgentPromptBehavior": { - "message": "Ask for authorization when using SSH agent" + "message": "در هنگام استفاده از عامل SSH درخواست تأیید مجوز شود" }, "sshAgentPromptBehaviorDesc": { - "message": "Choose how to handle SSH-agent authorization requests." + "message": "نحوه‌ی رسیدگی به درخواست‌های مجوز عامل SSH را انتخاب کنید." }, "sshAgentPromptBehaviorHelp": { - "message": "Remember SSH authorizations" + "message": "مجوزهای SSH را به خاطر بسپار" }, "sshAgentPromptBehaviorAlways": { - "message": "Always" + "message": "همیشه" }, "sshAgentPromptBehaviorNever": { - "message": "Never" + "message": "هرگز" }, "sshAgentPromptBehaviorRememberUntilLock": { - "message": "Remember until vault is locked" + "message": "تا زمانی که گاوصندوق قفل شود، به خاطر بسپار" }, "premiumRequired": { "message": "در نسخه پرمیوم کار می‌کند" @@ -268,17 +268,17 @@ "message": "خطا" }, "decryptionError": { - "message": "Decryption error" + "message": "خطای رمزگشایی" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden نتوانست مورد(های) گاوصندوق فهرست شده زیر را رمزگشایی کند." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "با بخش پشتیبانی مشتریان تماس بگیرید", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "برای جلوگیری از، از دست دادن داده‌های بیشتر.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "january": { @@ -355,7 +355,7 @@ "message": "تولید کلمه عبور" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "تولید عبارت عبور" }, "type": { "message": "نوع" @@ -412,16 +412,16 @@ "message": "کلید احراز هویت (TOTP)" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "کلید احراز هویت" }, "autofillOptions": { - "message": "Autofill options" + "message": "گزینه‌های پر کردن خودکار" }, "websiteUri": { - "message": "Website (URI)" + "message": "وب‌سایت (نشانی اینترنتی)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "وب‌سایت (نشانی اینترنتی) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -431,49 +431,49 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "وب‌سایت اضافه شد" }, "addWebsite": { - "message": "Add website" + "message": "افزودن وب‌سایت" }, "deleteWebsite": { - "message": "Delete website" + "message": "حذف وبسایت" }, "owner": { - "message": "Owner" + "message": "مالک" }, "addField": { - "message": "Add field" + "message": "افزودن فیلد" }, "editField": { - "message": "Edit field" + "message": "ویرایش فیلد" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "آیا مطمئن هستید که می‌خواهید این پرونده پیوست را به‌طور دائمی حذف کنید؟" }, "fieldType": { - "message": "Field type" + "message": "نوع فیلد" }, "fieldLabel": { - "message": "Field label" + "message": "برچسب فیلد" }, "add": { - "message": "Add" + "message": "افزودن" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "برای داده‌هایی مانند سوالات امنیتی از فیلدهای متنی استفاده کنید" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "برای داده‌های حساس مانند کلمه عبور از فیلدهای مخفی استفاده کنید" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "اگر می‌خواهید فیلدهای تیک‌دار فرم را به‌صورت خودکار پر کنید، مانند گزینه به یاد سپردن ایمیل، از کادرهای انتخاب استفاده کنید" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "وقتی در پر کردن خودکار برای یک وب‌سایت خاص به مشکل برخوردید، از فیلد مرتبط استفاده کنید." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "شناسه Html، نام، aria-label یا محل نگهدار فیلد را وارد کنید." }, "folder": { "message": "پوشه" @@ -501,7 +501,7 @@ "description": "This describes a field that is 'linked' (related) to another field." }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "کادر انتخاب" }, "linkedValue": { "message": "مقدار پیوند شده", @@ -535,7 +535,7 @@ "message": "مورد به زباله‌ها فرستاده شد" }, "overwritePasswordConfirmation": { - "message": "آیا از بازنویسی بر روی پسورد فعلی مطمئن هستید؟" + "message": "آیا از بازنویسی بر روی کلمه عبور فعلی مطمئن هستید؟" }, "overwriteUsername": { "message": "بازنویسی نام کاربری" @@ -560,13 +560,13 @@ "message": "کپی کلمه عبور" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "تولید مجدد کلید SSH" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "کپی کلید خصوصی SSH" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "کپی عبارت عبور", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -579,7 +579,7 @@ "message": "طول" }, "passwordMinLength": { - "message": "حداقل طول رمز عبور" + "message": "حداقل طول کلمه عبور" }, "uppercase": { "message": "حروف بزرگ (A-Z)", @@ -597,11 +597,11 @@ "message": "نویسه‌های ویژه (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "شامل", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "شامل حروف بزرگ باشد", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -609,7 +609,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "شامل حروف کوچک باشد", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -617,7 +617,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "شامل اعداد", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -625,7 +625,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "افزودن کاراکترهای خاص", "description": "Full description for the password generator special characters checkbox" }, "numWords": { @@ -656,11 +656,11 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "از کاراکترهای مبهم خودداری کن", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "نیازمندی‌های سیاست سازمانی بر گزینه‌های تولید کننده شما اعمال شده‌اند.", "description": "Indicates that a policy limits the credential generator screen." }, "searchCollection": { @@ -719,37 +719,37 @@ "message": "ایجاد حساب کاربری" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "در Bitwarden تازه وارد هستید؟" }, "setAStrongPassword": { - "message": "تنظیم رمز عبور قوی" + "message": "تنظیم کلمه عبور قوی" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "ایجاد حساب‌تان را با تنظیم کردن رمز عبور تکمیل کنید" + "message": "ایجاد حساب کاربری خود را با تنظیم کلمه عبور تکمیل کنید" }, "logIn": { "message": "ورود" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "وارد Bitwarden شوید" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "کد سامانه تأیید کننده را وارد نمایید" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "برای احراز هویت، کلید YubiKey خود را فشار دهید" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "با کلید عبور وارد شوید" }, "loginWithDevice": { - "message": "Log in with device" + "message": "ورود با دستگاه" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "استفاده از ورود تک مرحله‌ای" }, "submit": { "message": "ثبت" @@ -770,7 +770,7 @@ "message": "یادآور کلمه عبور اصلی (اختیاری)" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "اگر کلمه عبور خود را فراموش کنید، یادآور کلمه عبور می‌تواند به ایمیل شما ارسال شود. حداکثر $CURRENT$/$MAXIMUM$ کاراکتر.", "placeholders": { "current": { "content": "$1", @@ -783,19 +783,19 @@ } }, "masterPassword": { - "message": "Master password" + "message": "کلمه عبور اصلی" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "کلمه‌های عبور اصلی در صورت فراموشی قابل بازیابی نیستند!" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "تأیید کلمه عبور اصلی" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "یادآور کلمه عبور اصلی" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "امتیاز قدرت کلمه عبور $SCORE$", "placeholders": { "score": { "content": "$1", @@ -804,10 +804,10 @@ } }, "joinOrganization": { - "message": "Join organization" + "message": "به سازمان بپیوندید" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "به $ORGANIZATIONNAME$ بپیوندید", "placeholders": { "organizationName": { "content": "$1", @@ -816,22 +816,22 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "با تعیین یک کلمه عبور اصلی، عضویت خود در این سازمان را کامل کنید." }, "settings": { "message": "تنظیمات" }, "accountEmail": { - "message": "Account email" + "message": "حساب ایمیل" }, "requestHint": { - "message": "Request hint" + "message": "درخواست راهنمایی" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "درخواست یادآور کلمه عبور" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "نشانی ایمیل حساب کاربری خود را وارد کنید تا راهنمای کلمه عبور برای شما ارسال شود" }, "getMasterPasswordHint": { "message": "دریافت یادآور کلمه عبور اصلی" @@ -871,10 +871,10 @@ "message": "حساب کاربری جدید شما ساخته شد! حالا می‌توانید وارد شوید." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "حساب کاربری جدید شما ایجاد شده است!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "شما با موفقیت وارد شدید!" }, "masterPassSent": { "message": "ما یک ایمیل همراه با یادآور کلمه عبور اصلی برایتان ارسال کردیم." @@ -907,10 +907,10 @@ "message": "کد تأیید مورد نیاز است." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "احراز هویت لغو شد یا بیش از حد طول کشید. لطفاً دوباره تلاش کنید." }, "openInNewTab": { - "message": "Open in new tab" + "message": "گشودن در زبانهٔ جدید" }, "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" @@ -928,14 +928,14 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "در این دستگاه به مدت ۳۰ روز دوباره نپرس" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "انتخاب روش دیگر", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "از کد بازیابی‌تان استفاده کنید" }, "insertU2f": { "message": "کلید امنیتی خود را وارد پورت USB رایانه کنید، اگر دکمه‌ای دارد آن را بفشارید." @@ -950,17 +950,17 @@ "message": "برنامه احراز هویت" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "کدی را وارد کنید که توسط یک برنامه احراز هویت مانند Bitwarden Authenticator تولید شده است.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "کلید امنیت Yubico OTP" }, "yubiKeyDesc": { "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. همراه با دستگاه‌های YubiKey 4 ،4 Nano ،NEO کار می‌کند." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "کدی را وارد کنید که توسط Duo Security تولید شده است.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -968,13 +968,13 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "verifyYourIdentity": { - "message": "Verify your Identity" + "message": "هویت خود را تأیید کنید" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "ما این دستگاه را نمی‌شناسیم. برای تأیید هویت خود، کدی را که به ایمیلتان ارسال شده وارد کنید." }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "ادامه ورود" }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" @@ -986,7 +986,7 @@ "message": "ایمیل" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید." }, "loginUnavailable": { "message": "ورود به سیستم در دسترس نیست" @@ -1001,19 +1001,19 @@ "message": "گزینه‌های ورود دو مرحله‌ای" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "انتخاب ورود دو مرحله‌ای" }, "selfHostedEnvironment": { "message": "محیط خود میزبان" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "نشانی اینترنتی پایه نصب Bitwarden خود را که به‌صورت داخلی میزبانی شده مشخص کنید.\nمثال: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "برای پیکربندی پیشرفته، می‌توانید نشانی اینترنتی پایه هر سرویس را به‌صورت مستقل مشخص کنید." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, "customEnvironment": { "message": "محیط سفارشی" @@ -1022,13 +1022,13 @@ "message": "نشانی اینترنتی سرور" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "پایان زمان احراز هویت" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "نشست احراز هویت منقضی شد. لطفاً فرایند ورود را دوباره شروع کنید." }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "نشانی اینترنتی سرور خود میزبان", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1059,7 +1059,7 @@ "message": "خیر" }, "location": { - "message": "Location" + "message": "موقعیت" }, "overwritePassword": { "message": "بازنویسی کلمه عبور" @@ -1080,16 +1080,16 @@ "message": "نشست ورود شما منقضی شده است." }, "restartRegistration": { - "message": "Restart registration" + "message": "ثبت‌نام را دوباره آغاز کنید" }, "expiredLink": { - "message": "Expired link" + "message": "پیوند منقضی شد" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "لطفاً ثبت نام را مجدداً شروع کنید یا دوباره وارد شوید." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "ممکن است قبلاً حساب کاربری داشته باشید" }, "logOutConfirmation": { "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" @@ -1175,16 +1175,16 @@ "message": "گاوصندوق شما قفل شده است. برای ادامه هویت خود را تأیید کنید." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "حساب کاربری شما قفل شده است" }, "or": { - "message": "or" + "message": "یا" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "با استفاده از بیومتریک باز کنید" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "باز کردن قفل با کلمه عبور اصلی" }, "unlock": { "message": "باز کردن قفل" @@ -1215,7 +1215,7 @@ "message": "متوقف شدن گاو‌صندوق" }, "vaultTimeout1": { - "message": "Timeout" + "message": "پایان زمان" }, "vaultTimeoutDesc": { "message": "انتخاب کنید که گاو‌صندوق شما چه زمانی عمل توقف زمانی گاوصندوق را انجام دهد." @@ -1422,7 +1422,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "کپی ایمیل" }, "copySecurityCode": { "message": "کپی کد امنیتی", @@ -1453,7 +1453,7 @@ "message": "گزینه های ورود اضافی دو مرحله ای مانند YubiKey و Duo." }, "premiumSignUpReports": { - "message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." + "message": "گزارش‌های بهداشت کلمه عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." }, "premiumSignUpTotp": { "message": "تولید کننده کد تأیید (2FA) از نوع TOTP برای ورودهای موجود در گاوصندوقتان." @@ -1468,7 +1468,7 @@ "message": "خرید پرمیوم" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "می‌توانید نسخه پرمیوم را از تنظیمات حساب کاربری خود در اپلیکیشن وب Bitwarden خریداری کنید." }, "premiumCurrentMember": { "message": "شما یک عضو پرمیوم هستید!" @@ -1492,13 +1492,13 @@ "message": "تاریخچه کلمه عبور" }, "generatorHistory": { - "message": "Generator history" + "message": "تاریخچه تولید کننده" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "پاک کردن تاریخچه تولید کننده" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "اگر ادامه دهید، تمام ورودی‌ها به‌طور دائمی از تاریخچه تولید کننده حذف خواهند شد. آیا مطمئن هستید که می‌خواهید ادامه دهید؟" }, "clear": { "message": "پاک کردن", @@ -1508,13 +1508,13 @@ "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, "clearHistory": { - "message": "Clear history" + "message": "پاک کردن تاریخچه" }, "nothingToShow": { - "message": "Nothing to show" + "message": "چیزی برای نشان دادن موجود نیست" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "شما اخیراً چیزی تولید نکرده‌اید" }, "undo": { "message": "بازگرداندن" @@ -1591,13 +1591,13 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "کپی موفق بود" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "خطای به‌روزرسانی توکن دسترسی" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "هیچ توکن به‌روزرسانی یا کلید API یافت نشد. لطفاً از حساب کاربری خود خارج شده و دوباره وارد شوید." }, "help": { "message": "راهنما" @@ -1687,7 +1687,7 @@ "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "صادرات از" + "message": "برون ریزی از" }, "exportVault": { "message": "برون ریزی گاوصندوق" @@ -1696,31 +1696,31 @@ "message": "فرمت پرونده" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "این پرونده برون ریزی با کلمه عبور محافظت می‌شود و برای رمزگشایی به کلمه عبور پرونده نیاز دارد." }, "filePassword": { - "message": "رمز فایل" + "message": "کلمه عبور پرونده" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "این کلمه عبور برای برون ریزی و درون ریزی این پرونده استفاده می‌شود" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "برای رمزگذاری برون ریزی و محدود کردن درون ریزی فقط به حساب کاربری فعلی Bitwarden، از کلید رمزگذاری حساب خود که از نام کاربری و کلمه عبور اصلی حساب شما مشتق شده است استفاده کنید." }, "passwordProtected": { "message": "محافظت ‌شده با کلمه عبور" }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "یک کلمه عبور برای پرونده به‌منظور رمزگذاری تنظیم کنید تا برون ریزی و درون ریزی آن به هر حساب Bitwarden با استفاده از کلمه عبور رمزگشایی شود." }, "exportTypeHeading": { - "message": "نوع صادرات" + "message": "نوع برون ریزی" }, "accountRestricted": { "message": "حساب کاربری محدود شده است" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "عدم تطابق \"رمز فایل\" و \"تایید رمز فایل\" با یکدیگر." + "message": "عدم تطابق \"کلمه عبور پرونده\" و \"تأیید کلمه عبور پرونده\" با یکدیگر." }, "done": { "message": "انجام شد" @@ -1739,7 +1739,7 @@ "message": "این برون ریزی با استفاده از کلید رمزگذاری حساب شما، اطلاعاتتان را رمزگذاری می کند. اگر زمانی کلید رمزگذاری حساب خود را بچرخانید، باید دوباره خروجی بگیرید، چون قادر به رمزگشایی این پرونده برون ریزی نخواهید بود." }, "encExportAccountWarningDesc": { - "message": "کلیدهای رمزگذاری حساب برای هر حساب کاربری Bitwarden منحصر به فرد است، بنابراین نمی‌توانید برون ریزی رمزگذاری شده را به حساب دیگری وارد کنید." + "message": "کلیدهای رمزگذاری حساب برای هر حساب کاربری Bitwarden منحصر به فرد است، بنابراین نمی‌توانید برون ریزی رمزگذاری شده را به حساب دیگری درون ریزی کنید." }, "noOrganizationsList": { "message": "شما به هیچ سازمانی تعلق ندارید. سازمان‌ها به شما اجازه می‌دهند تا داده‌های خود را با کاربران دیگر به صورت امن به اشتراک بگذارید." @@ -1776,7 +1776,7 @@ "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { - "message": "باز کردن با پین" + "message": "باز کردن با کد پین" }, "setYourPinCode": { "message": "کد پین خود را برای باز کردن Bitwarden تنظیم کنید. اگر به طور کامل از برنامه خارج شوید، تنظیمات پین شما از بین می‌رود." @@ -1788,7 +1788,7 @@ "message": "کد پین غیر معتبر است." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "تعداد تلاش‌های ناموفق کد پین زیاد شد. خارج می‌شوید." }, "unlockWithWindowsHello": { "message": "باز کردن با Windows Hello" @@ -1797,7 +1797,7 @@ "message": "تنظیمات اضافی Windows Hello" }, "unlockWithPolkit": { - "message": "Unlock with system authentication" + "message": "باز کردن قفل با احراز هویت سیستم" }, "windowsHelloConsentMessage": { "message": "تأیید برای Bitwarden." @@ -1815,22 +1815,22 @@ "message": "درخواست Windows Hello در هنگام راه اندازی" }, "autoPromptPolkit": { - "message": "Ask for system authentication on launch" + "message": "در زمان اجرا درخواست احراز هویت سیستم را بده" }, "autoPromptTouchId": { "message": "درخواست Touch ID در هنگام راه اندازی" }, "requirePasswordOnStart": { - "message": "هنگام شروع برنامه، رمز عبور یا پین مورد نیاز است" + "message": "هنگام شروع برنامه، کلمه عبور یا کد پین مورد نیاز است" }, "requirePasswordWithoutPinOnStart": { - "message": "Require password on app start" + "message": "هنگام شروع برنامه، کلمه عبور مورد نیاز است" }, "recommendedForSecurity": { "message": "برای امنیت توصیه می‌شود." }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "در زمان شروع مجدد، با کلمه عبور اصلی قفل کن" }, "deleteAccount": { "message": "حذف حساب" @@ -1842,10 +1842,10 @@ "message": "حذف حساب شما دائمی است. نمی‌توان آن را برگرداند." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "قادر به حذف حساب کاربری نیستیم" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "این اقدام قابل انجام نیست زیرا حساب کاربری شما متعلق به یک سازمان است. برای جزئیات بیشتر با مدیر سازمان خود تماس بگیرید." }, "accountDeleted": { "message": "حساب حذف شد" @@ -1916,7 +1916,7 @@ "description": "Verb form: to make secure or inaccessible by" }, "trash": { - "message": "زباله‌ها", + "message": "سطل زباله", "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { @@ -1950,15 +1950,15 @@ "message": "تنظیم کلمه عبور اصلی" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "مجوزهای سازمان شما به‌روزرسانی شد، باید یک کلمه عبور اصلی تنظیم کنید.", "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": "سازمانتان از شما می‌خواهد که یک کلمه عبور اصلی تنظیم کنید.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "از میان $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1967,10 +1967,10 @@ } }, "cardDetails": { - "message": "Card details" + "message": "جزئیات کارت" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$ جزئیات", "placeholders": { "brand": { "content": "$1", @@ -1979,32 +1979,32 @@ } }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "درباره احراز هویت کننده‌ها بیشتر بدانید" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "کپی کلید احراز هویت (TOTP)" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "تأیید دو مرحله‌ای را بدون دردسر کنید" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. کلید را کپی کرده و در این فیلد قرار دهید." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. برای اسکن کد QR احراز هویت کننده این وب‌سایت، روی آیکون دوربین کلیک کنید یا کلید را کپی کرده و در این فیلد قرار دهید." }, "premium": { - "message": "Premium", + "message": "پرمیوم", "description": "Premium membership" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "سازمان‌های رایگان نمی‌توانند از پرونده‌های پیوست استفاده کنند" }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "۱ فیلد به توجه شما نیاز دارد." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "فیلدهای $COUNT$ به توجه شما نیاز دارند.", "placeholders": { "count": { "content": "$1", @@ -2013,13 +2013,13 @@ } }, "cardExpiredTitle": { - "message": "Expired card" + "message": "تاریخ کارت منقضی شده است" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "اگر تمدید کرده‌اید، اطلاعات کارت را به‌روزرسانی کنید" }, "verificationRequired": { - "message": "تایید مورد نیاز است", + "message": "تأیید لازم است", "description": "Default title for the user verification dialog." }, "currentMasterPass": { @@ -2074,10 +2074,10 @@ "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پیروی نمی‌کند." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "پیشنهادها، اعلان‌ها و فرصت‌های تحقیقاتی Bitwarden را در صندوق ورودی خود دریافت کنید." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "لغو اشتراک" }, "atAnyTime": { "message": "در هر زمان." @@ -2098,7 +2098,7 @@ "message": "فعال کردن ادغام مرورگر" }, "enableBrowserIntegrationDesc1": { - "message": "Used to allow biometric unlock in browsers that are not Safari." + "message": "برای فعال‌سازی باز کردن قفل با بیومتریک در مرورگرهایی به‌جز Safari استفاده می‌شود." }, "enableDuckDuckGoBrowserIntegration": { "message": "اجازه ادغام مرورگر DuckDuckGo را بدهید" @@ -2110,10 +2110,10 @@ "message": "ادغام مرورگر پشتیبانی نمی‌شود" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "خطای فعال کردن ادغام مرورگر" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "خطایی هنگام فعال‌سازی یکپارچه سازی مرورگر رخ داده است." }, "browserIntegrationMasOnlyDesc": { "message": "متأسفانه در حال حاضر ادغام مرورگر فقط در نسخه Mac App Store پشتیبانی می‌شود." @@ -2134,7 +2134,7 @@ "message": "استفاده از شتاب سخت افزاری" }, "enableHardwareAccelerationDesc": { - "message": "By default this setting is ON. Turn OFF only if you experience graphical issues. Restart is required." + "message": "به طور پیش‌فرض این تنظیم روشن است. فقط در صورت مواجهه با مشکلات گرافیکی آن را خاموش کنید. نیاز به راه‌اندازی مجدد دارد." }, "approve": { "message": "تأیید" @@ -2167,16 +2167,16 @@ "message": "بیومتریک مرورگر ابتدا نیاز به فعالسازی بیومتریک دسکتاپ در تنظیمات دارد." }, "biometricsManualSetupTitle": { - "message": "Automatic setup not available" + "message": "راه‌اندازی خودکار در دسترس نیست" }, "biometricsManualSetupDesc": { - "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + "message": "به دلیل روش نصب، پشتیبانی از بیومتریک به‌صورت خودکار فعال نشد. آیا می‌خواهید مستندات نحوه انجام این کار به‌صورت دستی را باز کنید؟" }, "personalOwnershipSubmitError": { "message": "به دلیل سیاست پرمیوم، برای ذخیره موارد در گاوصندوق شخصی خود محدود شده اید. گزینه مالکیت را به یک سازمان تغییر دهید و مجموعه های موجود را انتخاب کنید." }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "کلمه عبور جدید شما نمی‌تواند با کلمه عبور فعلی‌تان یکسان باشد." }, "hintEqualsPassword": { "message": "اشاره به کلمه عبور شما نمی‌تواند همان کلمه عبور شما باشد." @@ -2185,16 +2185,16 @@ "message": "سیاست سازمانی بر تنظیمات مالکیت شما تأثیر می‌گذارد." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "یک سیاست سازمانی، درون ریزی موارد به گاوصندوق فردی شما را مسدود کرده است." }, "personalDetails": { - "message": "Personal details" + "message": "جزئیات شخصی" }, "identification": { - "message": "Identification" + "message": "شناسایی" }, "contactInfo": { - "message": "Contact information" + "message": "اطلاعات تماس" }, "allSends": { "message": "همه ارسال ها", @@ -2360,10 +2360,10 @@ "message": "تأیید اعتبار در WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "خواندن کلید امنیتی" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "در انتظار تعامل با کلید امنیتی..." }, "hideEmail": { "message": "نشانی ایمیلم را از گیرندگان مخفی کن." @@ -2375,7 +2375,7 @@ "message": "تأیید ایمیل لازم است" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "ایمیل تأیید شد" }, "emailVerificationRequiredDesc": { "message": "برای استفاده از این ویژگی باید ایمیل خود را تأیید کنید." @@ -2402,22 +2402,22 @@ "message": "کلمه عبور اصلی شما با یک یا چند سیاست سازمان‌تان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "سازمان شما رمزگذاری دستگاه‌های مورد اعتماد را غیرفعال کرده است. لطفاً برای دسترسی به گاوصندوق خود یک کلمه عبور اصلی تنظیم کنید." }, "tryAgain": { "message": "دوباره سعی کنید" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "برای این اقدام تأیید لازم است. یک کد پین تعیین کنید تا ادامه دهید." }, "setPin": { - "message": "تنظیم PIN" + "message": "تنظیم کد پین" }, "verifyWithBiometrics": { - "message": "تایید با استفاده از بیومتریک" + "message": "تأیید با استفاده از بیومتریک" }, "awaitingConfirmation": { - "message": "در انتظار تایید" + "message": "در انتظار تأیید" }, "couldNotCompleteBiometrics": { "message": "تکمیل بیومتریک ممکن نشد." @@ -2426,10 +2426,10 @@ "message": "نیازمند روش دیگری هستید؟" }, "useMasterPassword": { - "message": "استفاده از رمز عبور اصلی" + "message": "استفاده از کلمه عبور اصلی" }, "usePin": { - "message": "استفاده از پین" + "message": "استفاده از کد پین" }, "useBiometrics": { "message": "استفاده از بیومتریک" @@ -2447,7 +2447,7 @@ "message": "دقیقه" }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "حداکثر $HOURS$ ساعت و $MINUTES$ دقیقه.", "placeholders": { "hours": { "content": "$1", @@ -2489,7 +2489,7 @@ "message": "مهلت زمانی شما بیش از محدودیت های تعیین شده توسط سازمانتان است." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "دعوتنامه پذیرفته شد" }, "resetPasswordPolicyAutoEnroll": { "message": "ثبت نام خودکار" @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "کلمه عبور اصلی حذف شد" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ در حال استفاده از SSO با یک سرور کلید خود میزبان است. برای ورود اعضای این سازمان دیگر نیازی به کلمه عبور اصلی نیست.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "برای اعضای سازمان زیر، کلمه عبور اصلی دیگر لازم نیست. لطفاً دامنه زیر را با مدیر سازمان خود تأیید کنید." + }, + "organizationName": { + "message": "نام سازمان" + }, + "keyConnectorDomain": { + "message": "دامنه رابط کلید" }, "leaveOrganization": { "message": "ترک سازمان" @@ -2582,7 +2582,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "فقط موردهای فردی گاوصندوق شامل پیوست‌ها که به $EMAIL$ مرتبط هستند برون ریزی خواهند شد. موردهای گاوصندوق سازمانی شامل نمی‌شوند", "placeholders": { "email": { "content": "$1", @@ -2591,10 +2591,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "در حال برون ریزی گاوصندوق سازمان" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "فقط گاوصدوق سازمان مرتبط با $ORGANIZATION$ برون ریزی خواهد شد. موارد موجود در گاوصندوق‌های فردی یا سایر سازمان‌ها شامل نمی‌شوند.", "placeholders": { "organization": { "content": "$1", @@ -2606,7 +2606,7 @@ "message": "قفل شده" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "گاوصندوق‌تان قفل شد" }, "unlocked": { "message": "باز شده" @@ -2628,13 +2628,13 @@ "message": "ایجاد نام کاربری" }, "generateEmail": { - "message": "Generate email" + "message": "تولید ایمیل" }, "usernameGenerator": { - "message": "Username generator" + "message": "تولید کننده نام کاربری" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "مقدار باید بین $MIN$ و $MAX$ باشد.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2648,7 +2648,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " برای تولید یک کلمه عبور قوی، از $RECOMMENDED$ کاراکتر یا بیشتر استفاده کنید.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2658,7 +2658,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " برای تولید یک عبارت عبور قوی، از $RECOMMENDED$ واژه یا بیشتر استفاده کنید.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2684,7 +2684,7 @@ "message": "از صندوق ورودی پیکربندی شده دامنه خود استفاده کنید." }, "useThisEmail": { - "message": "Use this email" + "message": "از این ایمیل استفاده شود" }, "random": { "message": "تصادفی" @@ -2714,15 +2714,15 @@ "message": "یک نام مستعار ایمیل با یک سرویس ارسال خارجی ایجاد کنید." }, "forwarderDomainName": { - "message": "Email domain", + "message": "دامنه ایمیل", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "دامنه‌ای را انتخاب کنید که توسط سرویس انتخاب شده پشتیبانی می‌شود", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ خطا: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2736,11 +2736,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "وب‌سایت: $WEBSITE$. تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2750,7 +2750,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "توکن API نامعتبر برای $SERVICENAME$", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2760,7 +2760,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "توکن API نامعتبر برای $SERVICENAME$: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2774,7 +2774,7 @@ } }, "forwaderInvalidOperation": { - "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "message": "$SERVICENAME$ درخواست شما را رد کرد. لطفاً برای دریافت کمک با ارائه‌دهنده سرویس خود تماس بگیرید.", "description": "Displayed when the user is forbidden from using the API by the forwarding service.", "placeholders": { "servicename": { @@ -2784,7 +2784,7 @@ } }, "forwaderInvalidOperationWithMessage": { - "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "message": "$SERVICENAME$ درخواست شما را رد کرد: $ERRORMESSAGE$", "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2798,7 +2798,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "دریافت شناسه حساب ایمیل ماسک شده از $SERVICENAME$ امکان‌پذیر نیست.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2808,7 +2808,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "دامنه $SERVICENAME$ نامعتبر.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2818,7 +2818,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "آدرس $SERVICENAME$ نامعتبر.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2828,7 +2828,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "خطای $SERVICENAME$ نامعلومی رخ داد.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2838,7 +2838,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "فرواردکننده ناشناخته: $SERVICENAME$.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2897,25 +2897,25 @@ "message": "ورود به سیستم آغاز شد" }, "logInRequestSent": { - "message": "Request sent" + "message": "درخواست ارسال شد" }, "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "یک اعلان به دستگاه شما ارسال شده است" }, "notificationSentDevicePart1": { - "message": "Unlock Bitwarden on your device or on the " + "message": "قفل Bitwarden را روی دستگاه خود باز کنید یا روی " }, "notificationSentDeviceAnchor": { - "message": "web app" + "message": "برنامه وب" }, "notificationSentDevicePart2": { - "message": "Make sure the Fingerprint phrase matches the one below before approving." + "message": "اطمینان حاصل کنید که عبارت اثر انگشت با عبارت زیر مطابقت دارد قبل از تأیید." }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "به گزینه دیگری نیاز دارید؟" }, "fingerprintMatchInfo": { "message": "لطفاً مطمئن شوید که قفل گاوصندوق شما باز است و عبارت اثر انگشت در دستگاه دیگر مطابقت دارد." @@ -2924,13 +2924,13 @@ "message": "عبارت اثر انگشت" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "به‌محض تأیید درخواست، به شما اطلاع داده خواهد شد" }, "needAnotherOption": { "message": "ورود به سیستم با دستگاه باید در تنظیمات برنامه‌ی Bitwarden تنظیم شود. به گزینه دیگری نیاز دارید؟" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "مشاهده همه گزینه‌های ورود به سیستم" }, "viewAllLoginOptions": { "message": "مشاهده همه گزینه‌های ورود به سیستم" @@ -2943,10 +2943,10 @@ "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" + "message": "آیا در تلاش برای دسترسی به حساب کاربری خود هستید؟" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "تلاش برای دسترسی به سیستم توسط $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2964,10 +2964,10 @@ "message": "زمان" }, "confirmAccess": { - "message": "Confirm access" + "message": "تأیید دسترسی" }, "denyAccess": { - "message": "Deny access" + "message": "دسترسی را رد کن" }, "logInConfirmedForEmailOnDevice": { "message": "ورود به سیستم برای $EMAIL$ در $DEVICE$ تأیید شد", @@ -3004,7 +3004,7 @@ "message": "این درخواست دیگر معتبر نیست." }, "confirmAccessAttempt": { - "message": "Confirm access attempt for $EMAIL$", + "message": "تأیید تلاش برای دسترسی به سیستم برای $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -3016,28 +3016,28 @@ "message": "ورود الزامیست" }, "accountAccessRequested": { - "message": "Account access requested" + "message": "درخواست دسترسی به حساب کاربری دریافت شد" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "در حال ساخت حساب روی" }, "checkYourEmail": { - "message": "Check your email" + "message": "ایمیل خود را چک کنید" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "دنبال کنید لینکی را که در ایمیل فرستاده شده به" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "و به ساختن حساب‌تان ادامه دهید." }, "noEmail": { - "message": "No email?" + "message": "ایمیلی دریافت نکردید؟" }, "goBack": { - "message": "Go back" + "message": "بازگشت به عقب" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "برای ویرایش آدرس ایمیل خود." }, "exposedMasterPassword": { "message": "کلمه عبور اصلی افشا شده" @@ -3052,28 +3052,28 @@ "message": "کلمه عبور ضعیف شناسایی و در یک نقض داده پیدا شد. از یک کلمه عبور قوی و منحصر به فرد برای محافظت از حساب خود استفاده کنید. آیا مطمئنید که می‌خواهید از این کلمه عبور استفاده کنید؟" }, "useThisPassword": { - "message": "Use this password" + "message": "از این کلمه عبور استفاده کن" }, "useThisUsername": { - "message": "Use this username" + "message": "از این نام کاربری استفاده کن" }, "checkForBreaches": { "message": "نقض اطلاعات شناخته شده برای این کلمه عبور را بررسی کنید" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "وارد شده!" }, "important": { "message": "مهم:" }, "accessing": { - "message": "Accessing" + "message": "در حال دسترسی" }, "accessTokenUnableToBeDecrypted": { - "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + "message": "شما خارج شده‌اید زیرا توکن دسترسی شما قابل رمزگشایی نبود. لطفاً برای رفع این مشکل دوباره وارد شوید." }, "refreshTokenSecureStorageRetrievalFailure": { - "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + "message": "شما خارج شده‌اید زیرا توکن تازه‌سازی شما قابل بازیابی نبود. لطفاً برای رفع این مشکل دوباره وارد شوید." }, "masterPasswordHint": { "message": "کلمه عبور اصلی شما در صورت فراموشی قابل بازیابی نیست!" @@ -3088,22 +3088,22 @@ } }, "windowsBiometricUpdateWarning": { - "message": "Bitwarden توصیه می‌کند که تنظیمات بیومتریک خود را به‌روزرسانی کنید تا در اولین باز کردن قفل، به رمز عبور اصلی (یا پین) نیاز داشته باشید. آیا می‌خواهید تنظیمات خود را اکنون به‌روز کنید؟" + "message": "Bitwarden توصیه می‌کند که تنظیمات بیومتریک خود را به‌روزرسانی کنید تا در اولین باز کردن قفل، به کلمه عبور اصلی (یا کد پین) نیاز داشته باشید. آیا می‌خواهید تنظیمات خود را اکنون به‌روز کنید؟" }, "windowsBiometricUpdateWarningTitle": { "message": "به‌روز رسانی تنظیمات توصیه شده" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "این دستگاه را به خاطر بسپار تا ورودهای بعدی بدون مشکل انجام شود" }, "deviceApprovalRequired": { "message": "تأیید دستگاه لازم است. یک روش تأیید انتخاب کنید:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "تأیید دستگاه لازم است" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "یکی از گزینه‌های تأیید زیر را انتخاب کنید" }, "rememberThisDevice": { "message": "این دستگاه را به خاطر بسپار" @@ -3152,34 +3152,34 @@ "message": "ایمیل کاربر وجود ندارد" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "ایمیل کاربر فعال پیدا نشد. در حال خارج کردن شما از سیستم هستیم." }, "deviceTrusted": { "message": "دستگاه مورد اعتماد است" }, "trustOrganization": { - "message": "Trust organization" + "message": "اعتماد به سازمان" }, "trust": { - "message": "Trust" + "message": "اطمینان" }, "doNotTrust": { - "message": "Do not trust" + "message": "اعتماد نکنید" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "سازمان مورد اعتماد نیست" }, "emergencyAccessTrustWarning": { - "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" + "message": "برای امنیت حساب کاربری شما، فقط در صورتی تأیید کنید که دسترسی اضطراری به این کاربر داده‌اید و اثر انگشت او با آنچه در حسابش نمایش داده شده، مطابقت دارد" }, "orgTrustWarning": { - "message": "For the security of your account, only proceed if you are a member of this organization, have account recovery enabled, and the fingerprint displayed below matches the organization's fingerprint." + "message": "برای امنیت حساب کاربری شما، فقط در صورتی ادامه دهید که عضو این سازمان باشید، بازیابی حساب کاربری را فعال کرده باشید و اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت داشته باشد." }, "orgTrustWarning1": { - "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + "message": "این سازمان دارای سیاست سازمانی است که شما را در بازیابی حساب کاربری ثبت‌نام می‌کند. ثبت‌نام به مدیران سازمان اجازه می‌دهد کلمه عبور شما را تغییر دهند. فقط در صورتی ادامه دهید که این سازمان را می‌شناسید و عبارت اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت دارد." }, "trustUser": { - "message": "Trust user" + "message": "به کاربر اعتماد کنید" }, "inputRequired": { "message": "ورودی ضروری است." @@ -3282,7 +3282,7 @@ "message": "زیرمنو" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "تغییر وضعیت ناوبری کناری" }, "skipToContent": { "message": "پرش به محتوا" @@ -3300,14 +3300,14 @@ "message": "دامنه مستعار" }, "importData": { - "message": "وارد کردن اطلاعات", + "message": "درون ریزی داده", "description": "Used for the desktop menu item and the header of the import dialog" }, "importError": { - "message": "خطای وارد کردن" + "message": "خطای درون ریزی" }, "importErrorDesc": { - "message": "مشکلی با داده‌هایی که سعی کردید وارد کنید وجود داشت. لطفاً خطاهای فهرست شده در زیر را در فایل منبع خود برطرف کرده و دوباره امتحان کنید." + "message": "مشکلی با داده‌هایی که سعی کردید درون ریزی کنید وجود داشت. لطفاً خطاهای فهرست شده در زیر را در پرونده منبع خود برطرف کرده و دوباره امتحان کنید." }, "resolveTheErrorsBelowAndTryAgain": { "message": "خطاهای زیر را برطرف کرده و دوباره امتحان کنید." @@ -3316,10 +3316,10 @@ "message": "توضیحات" }, "importSuccess": { - "message": "داده‌ها با موفقیت وارد شد" + "message": "داده‌ها با موفقیت درون ریزی شد" }, "importSuccessNumberOfItems": { - "message": "$AMOUNT$ مورد وارد شده است.", + "message": "$AMOUNT$ مورد درون ریزی شده است.", "placeholders": { "amount": { "content": "$1", @@ -3331,7 +3331,7 @@ "message": "مجموع" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "شما در حال درون ریزی داده به $ORGANIZATION$ هستید. داده‌های شما ممکن است با اعضای این سازمان به اشتراک گذاشته شود. آیا می‌خواهید ادامه دهید؟", "placeholders": { "organization": { "content": "$1", @@ -3340,49 +3340,49 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "خطا در اتصال به سرویس Duo. از روش ورود دو مرحله‌ای دیگری استفاده کنید یا برای دریافت کمک با Duo تماس بگیرید." }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "ورود دو مرحله ای Duo برای حساب کاربری شما لازم است." }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "برای حساب کاربری شما ورود دو مرحله‌ای Duo لازم است. مراحل زیر را دنبال کنید تا ورود خود را کامل کنید." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "مراحل زیر را دنبال کنید تا وارد سیستم شوید." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Follow the steps below to finish logging in with your security key." + "message": "مراحل زیر را برای کامل کردن ورود با کلید امنیتی خود دنبال کنید." }, "launchDuo": { - "message": "Launch Duo in Browser" + "message": "اجرای Duo در مرورگر" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "داده‌ها به درستی قالب‌بندی نشده‌اند. لطفاً پرونده درون ریزی شده خود را بررسی و دوباره امتحان کنید." }, "importNothingError": { - "message": "چیزی وارد نشد." + "message": "چیزی درون ریزی نشد." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "خطا در رمزگشایی پرونده‌ی درون ریزی شده. کلید رمزگذاری شما با کلید رمزگذاری استفاده شده برای درون ریزی داده‌ها مطابقت ندارد." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "کلمه عبور پرونده نامعتبر است، لطفاً از کلمه عبوری که هنگام ایجاد پرونده‌ی برون ریزی وارد کردید استفاده کنید." }, "destination": { - "message": "Destination" + "message": "مقصد" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "درباره گزینه‌های درون ریزی خود بیاموزید" }, "selectImportFolder": { "message": "یک پوشه انتخاب کنید" }, "selectImportCollection": { - "message": "انتخاب یک مجموعه" + "message": "یک مجموعه انتخاب کنید" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "اگر می‌خواهید محتوای پرونده درون ریزی شده به $DESTINATION$ منتقل شود، این گزینه را انتخاب کنید", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -3392,25 +3392,25 @@ } }, "importUnassignedItemsError": { - "message": "فایل حاوی موارد اختصاص نیافته است." + "message": "پرونده حاوی موارد اختصاص نیافته است." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "فرمت پرونده‌ی درون ریزی را انتخاب کنید" }, "selectImportFile": { - "message": "انتخاب فایل وارد شده" + "message": "پرونده‌ی درون ریزی را انتخاب کنید" }, "chooseFile": { - "message": "انتخاب فایل" + "message": "انتخاب پرونده" }, "noFileChosen": { - "message": "هیچ فایلی انتخاب نشد" + "message": "هیچ پرونده‌ای انتخاب نشد" }, "orCopyPasteFileContents": { - "message": "یا محتویات فایل وارد شده را کپی/پیست کنید" + "message": "یا محتویات پرونده‌ی درون ریزی را کپی/پیست کنید" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "دستورالعمل‌های $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -3420,120 +3420,120 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "درون ریزی گاوصندوق را تأیید کنید" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "این پرونده با کلمه عبور محافظت شده است. لطفاً کلمه عبور پرونده را برای درون ریزی داده‌ها وارد کنید." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "تأیید کلمه عبور پرونده" }, "exportSuccess": { - "message": "Vault data exported" + "message": "داده‌های گاوصندوق برون ریزی شد" }, "multifactorAuthenticationCancelled": { - "message": "تایید هویت چندمرحله‌ای کنسل شد" + "message": "تایید هویت چند مرحله‌ای کنسل شد" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "هیچ داده‌ای از LastPass یافت نشد" }, "incorrectUsernameOrPassword": { - "message": "نام کاربری یا گذرواژه نادرست است" + "message": "نام کاربری یا کلمه عبور اشتباه است" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "کلمه عبور اشتباه است" }, "incorrectCode": { - "message": "Incorrect code" + "message": "کد اشتباه است" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "کد پین نادرست است" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "احراز هویت چند مرحله‌ای ناموفق بود" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "شامل پوشه‌های به‌ اشتراک گذاری‌ شده" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "ایمیل LastPass" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "در حال درون ریزی حساب کاربری شما..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "احراز هویت چند مرحله‌ای LastPass مورد نیاز است" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "کد یک‌بار مصرف خود را از برنامه احراز هویت وارد کنید" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "درخواست ورود را در برنامه احراز هویت خود تأیید کنید یا یک کد یک‌بار مصرف وارد کنید." }, "passcode": { - "message": "Passcode" + "message": "کد عبور" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "کلمه عبور اصلی LastPass" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "احراز هویت LastPass مورد نیاز است" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "در انتظار احراز هویت SSO" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "لطفاً برای ورود، از اطلاعات کاربری شرکت خود استفاده کنید." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "دستورالعمل‌های کامل را در سایت راهنمای ما مشاهده کنید در", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "درون ریزی مستقیم از LastPass" }, "importFromCSV": { - "message": "وارد کردن از CSV" + "message": "درون ریزی از CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "دوباره تلاش کنید یا ایمیلی از LastPass را بررسی کنید تا هویت شما تأیید شود." }, "collection": { "message": "مجموعه" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "کلید YubiKey مربوط به حساب کاربری LastPass خود را در درگاه USB کامپیوتر قرار دهید، سپس دکمه آن را لمس کنید." }, "commonImportFormats": { "message": "فرمت‌های رایج", "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "موفقیت آمیز بود" }, "troubleshooting": { - "message": "Troubleshooting" + "message": "عیب یابی" }, "disableHardwareAccelerationRestart": { - "message": "Disable hardware acceleration and restart" + "message": "شتاب‌دهی سخت‌افزاری را غیرفعال کنید و دوباره راه‌اندازی کنید" }, "enableHardwareAccelerationRestart": { - "message": "Enable hardware acceleration and restart" + "message": "شتاب‌دهی سخت‌افزاری را فعال کنید و دوباره راه‌اندازی کنید" }, "removePasskey": { - "message": "Remove passkey" + "message": "حذف کلید عبور" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "کلید عبور حذف شد" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "خطا در اختصاص مجموعه هدف." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "خطا در اختصاص پوشه هدف." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "مشاهده موارد در $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3543,7 +3543,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "بازگشت به $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3553,11 +3553,11 @@ } }, "back": { - "message": "Back", + "message": "بازگشت", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "حذف $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3567,42 +3567,42 @@ } }, "data": { - "message": "Data" + "message": "داده‌" }, "fileSends": { - "message": "File Sends" + "message": "پرونده ارسال‌ها" }, "textSends": { - "message": "Text Sends" + "message": "ارسال‌های متن" }, "ssoError": { - "message": "No free ports could be found for the sso login." + "message": "هیچ پورت رایگانی برای ورود sso یافت نشد." }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "کلمه عبور ایمن ساخته شد! فراموش نکنید کلمه عبور خود را در وب‌سایت نیز به‌روزرسانی کنید." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "از تولید کننده استفاده کنید", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "برای ایجاد یک کلمه عبور قوی و منحصر به فرد", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "بازکردن با بیومتریک در دسترس نیست زیرا ابتدا باید بازکردن قفل با کد پین یا کلمه عبور انجام شود." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "باز کردن قفل با بیومتریک در حال حاضر در دسترس نیست." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "باز کردن قفل با بیومتریک به دلیل پیکربندی نادرست پرونده‌های سیستم در دسترس نیست." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "باز کردن قفل با بیومتریک به دلیل پیکربندی نادرست پرونده‌های سیستم در دسترس نیست." }, "biometricsStatusHelptextNotEnabledLocally": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "باز کردن قفل با بیومتریک در دسترس نیست زیرا برای $EMAIL$ در برنامه دسکتاپ Bitwarden فعال نشده است.", "placeholders": { "email": { "content": "$1", @@ -3611,156 +3611,165 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "باز کردن قفل با بیومتریک در حال حاضر به دلیل نامعلومی در دسترس نیست." }, "itemDetails": { - "message": "Item details" + "message": "جزئیات مورد" }, "itemName": { - "message": "Item name" + "message": "نام مورد" }, "loginCredentials": { - "message": "Login credentials" + "message": "اطلاعات ورود" }, "additionalOptions": { - "message": "Additional options" + "message": "گزینه‌های اضافی" }, "itemHistory": { - "message": "Item history" + "message": "تاریخچه مورد" }, "lastEdited": { - "message": "Last edited" + "message": "آخرین ویرایش" }, "upload": { - "message": "Upload" + "message": "بارگذاری" }, "authorize": { - "message": "Authorize" + "message": "اجازه دادن" }, "deny": { - "message": "Deny" + "message": "رد کن" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "تأیید استفاده از کلید SSH" }, "agentForwardingWarningTitle": { - "message": "Warning: Agent Forwarding" + "message": "هشدار: انتقال عامل" }, "agentForwardingWarningText": { - "message": "This request comes from a remote device that you are logged into" + "message": "این درخواست از دستگاه راه دوری ارسال شده است که شما در آن وارد شده‌اید" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "درخواست دسترسی دارد به" }, "sshkeyApprovalMessageSuffix": { - "message": "in order to" + "message": "برای اینکه بتواند" }, "sshActionLogin": { - "message": "authenticate to a server" + "message": "برای احراز هویت به یک سرور" }, "sshActionSign": { - "message": "sign a message" + "message": "امضای یک پیام" }, "sshActionGitSign": { - "message": "sign a git commit" + "message": "امضای یک کامیت گیت" }, "unknownApplication": { - "message": "An application" + "message": "یک برنامه" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "کلید SSH نامعتبر است" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "نوع کلید SSH پشتیبانی نمی‌شود" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "وارد کردن کلید از حافظه موقت" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "کلید SSH با موفقیت وارد شد" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "پرونده در دستگاه ذخیره شد. از بخش بارگیری‌های دستگاه خود مدیریت کنید." }, "allowScreenshots": { - "message": "Allow screen capture" + "message": "اجازه ضبط صفحه" }, "allowScreenshotsDesc": { - "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + "message": "اجازه دهید برنامه دسکتاپ Bitwarden در اسکرین‌شات‌ها ثبت شده و در جلسات دسکتاپ از راه دور مشاهده شود. غیرفعال کردن این گزینه دسترسی در برخی نمایشگرهای خارجی را مسدود می‌کند." }, "confirmWindowStillVisibleTitle": { - "message": "Confirm window still visible" + "message": "تأیید کنید که پنجره هنوز قابل مشاهده است" }, "confirmWindowStillVisibleContent": { - "message": "Please confirm that the window is still visible." + "message": "لطفاً تأیید کنید که پنجره هنوز قابل مشاهده است." }, "updateBrowserOrDisableFingerprintDialogTitle": { - "message": "Extension update required" + "message": "به‌روزرسانی افزونه لازم است" }, "updateBrowserOrDisableFingerprintDialogMessage": { - "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + "message": "افزونه مرورگری که استفاده می‌کنید قدیمی است. لطفاً آن را به‌روزرسانی کنید یا اعتبارسنجی اثر انگشت یکپارچه‌سازی مرورگر را در تنظیمات برنامه دسکتاپ غیرفعال کنید." }, "changeAtRiskPassword": { - "message": "Change at-risk password" + "message": "تغییر کلمه عبور در معرض خطر" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } }, "move": { - "message": "Move" + "message": "انتقال" }, "newFolder": { - "message": "New folder" + "message": "پوشه جدید" }, "folderName": { - "message": "Folder Name" + "message": "نام پوشه" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "برای تو در تو کردن یک پوشه، نام پوشه والد را وارد کرده و سپس یک “/” اضافه کنید. مثال: Social/Forums" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "با پر کردن خودکار در وقت خود صرفه جویی کنید" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "شامل یک", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "وب‌سایت", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "تا این ورود به عنوان پیشنهاد پر کردن خودکار ظاهر شود.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newCardNudgeTitle": { - "message": "Seamless online checkout" + "message": "پرداخت آنلاین بدون وقفه" }, "newCardNudgeBody": { - "message": "With cards, easily autofill payment forms securely and accurately." + "message": "با کارت‌ها، فرم‌های پرداخت را به‌راحتی و با امنیت و دقت پر کنید." }, "newIdentityNudgeTitle": { - "message": "Simplify creating accounts" + "message": "ساخت حساب‌های کاربری را ساده کنید" }, "newIdentityNudgeBody": { - "message": "With identities, quickly autofill long registration or contact forms." + "message": "با هویت‌ها، به سرعت فرم‌های طولانی ثبت‌نام یا تماس را پر کنید." }, "newNoteNudgeTitle": { - "message": "Keep your sensitive data safe" + "message": "اطلاعات حساس خود را ایمن نگه دارید" }, "newNoteNudgeBody": { - "message": "With notes, securely store sensitive data like banking or insurance details." + "message": "با یادداشت‌ها، اطلاعات حساسی مانند جزئیات بانکی یا بیمه را به‌صورت ایمن ذخیره کنید." }, "newSshNudgeTitle": { - "message": "Developer-friendly SSH access" + "message": "دسترسی SSH مناسب برای توسعه‌دهندگان" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "کلیدهای خود را ذخیره کنید و با عامل SSH برای احراز هویت سریع و رمزگذاری‌شده متصل شوید.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "اطلاعات بیشتر درباره عامل SSH را بیاموزید", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 14cf9c95ffc..bffa90c8a17 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -247,10 +247,10 @@ "message": "Remember SSH authorizations" }, "sshAgentPromptBehaviorAlways": { - "message": "Always" + "message": "Aina" }, "sshAgentPromptBehaviorNever": { - "message": "Never" + "message": "Ei koskaan" }, "sshAgentPromptBehaviorRememberUntilLock": { "message": "Remember until vault is locked" @@ -446,7 +446,7 @@ "message": "Lisää kenttä" }, "editField": { - "message": "Edit field" + "message": "Muokkaa kenttää" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" @@ -1998,13 +1998,13 @@ "description": "Premium membership" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "Ilmaiset organisaatiot eivät voi käyttää liitteitä" }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "Yksi kenttä vaatii huomiotasi." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ kenttää vaatii huomiotasi.", "placeholders": { "count": { "content": "$1", @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Pääsalasana poistettiin" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ käyttää kertakirjautumista (SSO) itse ylläpidetyllä avainpalvelimella. Organisaation jäsenet eivät enää tarvitse pääsalasanaa kirjautumiseen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Poistu organisaatiosta" @@ -3158,16 +3158,16 @@ "message": "Laitteeseen luotettu" }, "trustOrganization": { - "message": "Trust organization" + "message": "Luota organisaatioon" }, "trust": { - "message": "Trust" + "message": "Luota" }, "doNotTrust": { - "message": "Do not trust" + "message": "Älä luota" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "Organisaatio ei ole luotettu" }, "emergencyAccessTrustWarning": { "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" @@ -3179,7 +3179,7 @@ "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." }, "trustUser": { - "message": "Trust user" + "message": "Luota käyttäjään" }, "inputRequired": { "message": "Syöte vaaditaan." @@ -3614,25 +3614,25 @@ "message": "Biometrinen avaus ei ole tällä hetkellä käytettävissä tuntemattomasta syystä." }, "itemDetails": { - "message": "Item details" + "message": "Kohteen tiedot" }, "itemName": { - "message": "Item name" + "message": "Kohteen nimi" }, "loginCredentials": { - "message": "Login credentials" + "message": "Kirjautumistiedot" }, "additionalOptions": { - "message": "Additional options" + "message": "Lisävalinnat" }, "itemHistory": { - "message": "Item history" + "message": "Kohteen historia" }, "lastEdited": { - "message": "Last edited" + "message": "Viimeksi muokattu" }, "upload": { - "message": "Upload" + "message": "Lähetä" }, "authorize": { "message": "Valtuuta" @@ -3703,20 +3703,29 @@ "changeAtRiskPassword": { "message": "Vaihda vaarantunut salasana" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Siirrä" }, "newFolder": { - "message": "New folder" + "message": "Uusi kansio" }, "folderName": { - "message": "Folder Name" + "message": "Kansion nimi" }, "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "Säästä aikaa automaattitäytöllä" }, "newLoginNudgeBodyOne": { "message": "Include a", @@ -3724,7 +3733,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Verkkosivusto", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index b3633cd1694..74c886a98b6 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Tinanggal ang password ng master" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ay gumagamit ng SSO na may self-hosted key server. Ang isang master password ay hindi na kinakailangan upang mag log in para sa mga miyembro ng organisasyong ito.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Umalis sa organisasyon" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index e94f4837d99..2c53c0652bd 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Mot de passe principal supprimé" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ utilise le SSO avec un serveur de clés auto-hébergé. Un mot de passe principal n'est plus nécessaire aux membres de cette organisation pour se connecter.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Quitter l'organisation" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Changer le mot de passe à risque" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 471d7235945..17495f96785 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 954c50812be..3999caa1bbd 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "הסיסמה הראשית הוסרה" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ משתמש/ת ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית כבר לא נדרשת כדי להיכנס עבור חברים של ארגון זה.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "עזוב ארגון" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "שנה סיסמה בסיכון" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 10307cc03ca..292e1c4b7d2 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 8bdfcb40534..cf90703a438 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Glavna lozinka uklonjena." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ koristi jedinstvenu prijavu SSO s vlastitim poslužiteljem. Članovima organizacije glavna lozinka više nije potrebna.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Napusti organizaciju" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Promijeni rizičnu lozinku" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index e56b62f9840..f7eb77766c1 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "A mesterjelszó eltávolításra került." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ jelenleg saját tárolású aláíráskulcsú SSO szervert használ. A mesterjelszó a továbbiakban nem szükséges a szervezeti tagsági bejelentkezéshez.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A következő szervezet tagjai számára már nincs szükség mesterjelszóra. Erősítsük meg az alábbi tartományt a szervezet adminisztrátorával." + }, + "organizationName": { + "message": "Szervezet neve" + }, + "keyConnectorDomain": { + "message": "Key Connector tartomány" }, "leaveOrganization": { "message": "Szervezet elhagyása" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Kockázatos jelszó megváltoztatása" }, + "cannotRemoveViewOnlyCollections": { + "message": "Nem távolíthatók el a csak megtekintési engedéllyel bíró gyűjtemények: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Áthelyezés" }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 1eeb32fdcc6..70be61f4bf3 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Sandi utama dihapus" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ menggunakan SSO dengan server kunci yang dihosting sendiri. Kata sandi utama tidak lagi diperlukan untuk masuk untuk anggota organisasi ini.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Tinggalkan organisasi" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index db70740d96e..a3dcb13fa76 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Password principale rimossa" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ sta usando SSO con un server self-hosted. Una password principale non è più necessaria per accedere per i membri di questa organizzazione.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lascia organizzazione" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Modifica la password non sicura o esposta" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 47ba77508bc..624a296f32b 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "マスターパスワードを削除しました。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ は自己ホストの鍵サーバで SSO を使用しています。この組織のメンバーのログインにマスターパスワードは必要ありません。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "組織から脱退する" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "危険なパスワードの変更" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "移動" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 6a2912eed10..45047cd678f 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 471d7235945..17495f96785 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 767cdcf9d03..e74fc479016 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index b7b2a82b4b5..371f4cae1c8 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "마스터 비밀번호가 제거되었습니다." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 조직은 자체 호스팅 키 서버로 SSO를 사용하고 있습니다 이 조직의 멤버들은 로그인할 때에 마스터 비밀번호를 필요로 하지 않습니다.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "조직 나가기" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 66dcf1da155..f98a5dba2e1 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Pagrindinis slaptažodis pašalintas" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ naudoja SSO su savarankiškai sukurtu raktų serveriu. Pagrindinis slaptažaodis nebėra reikalingas norint šios organizacijos nariams prisijungti.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Palikti organizaciją" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 437aba39714..6c35520605d 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Galvenā parole tika noņemta." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ izmanto vienoto pieteikšanos ar pašmitinātu atslēgu serveri. Tās dalībniekiem vairs nav nepieciešama galvenā parole, lai pieteiktos.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Galvenā parole vairs nav nepieciešama turpmāk minētās apvienības dalībniekiem. Lūgums saskaņot zemāk esošo domēnu ar savas apvienības pārvaldītāju." + }, + "organizationName": { + "message": "Apvienības nosaukums" + }, + "keyConnectorDomain": { + "message": "Key Connector domēns" }, "leaveOrganization": { "message": "Pamest apvienību" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Mainīt riskam pakļautu paroli" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Pārvietot" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 152877fa552..51310719f0a 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 3fcb52bade5..a760acb86ec 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 471d7235945..17495f96785 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 724194808cf..2179311c501 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 5522218037f..3ddf3e44e5f 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hovedpassordet er fjernet." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruker SSO med en selvdrevet nøkkelserver. Et hovedpassord er ikke lenger nødvendig for å logge inn for medlemmer av denne organisasjonen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlat organisasjon" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Flytt" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index a56268aa671..9ae7b7af955 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index cad8a495dbf..b1e04600908 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hoofdwachtwoord verwijderd." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruikt SSO met een zelf gehoste sleutelserver. Leden van deze organisatie kunnen inloggen zonder hoofdwachtwoord.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Voor leden van de volgende organisatie is een hoofdwachtwoord niet langer nodig. Bevestig het domein hieronder met de beheerder van je organisatie." + }, + "organizationName": { + "message": "Organisatienaam" + }, + "keyConnectorDomain": { + "message": "Key Connector-domein" }, "leaveOrganization": { "message": "Organisatie verlaten" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Risicovol wachtwoord wijzigen" }, + "cannotRemoveViewOnlyCollections": { + "message": "Je kunt verzamelingen niet verwijderen met alleen rechten voor weergeven: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Verplaatsen" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index b3f0f0c5d20..097da5886a6 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index d584cd5d117..a71275e64ac 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 61c438c0425..84ef8f0ca9a 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hasło główne zostało usunięte" }, - "convertOrganizationEncryptionDesc": { - "message": "Organizacja $ORGANIZATION$ używa jednokrotnego logowania SSO z własnym serwerem kluczy. Użytkownicy nie muszą logować się za pomocą hasła głównego.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Opuść organizację" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Zmień zagrożone hasło" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index e44bb7abc8c..43fa1087988 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Senha mestra removida." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO com um servidor de chaves auto-hospedado. Não é mais necessária uma senha mestra para os membros desta organização entrarem.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Sair da Organização" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Alterar senhas vulneráveis" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Mover" }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index ef657f1b658..dc40c91960e 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Palavra-passe mestra removida" }, - "convertOrganizationEncryptionDesc": { - "message": "A $ORGANIZATION$ está a utilizar o SSO com um servidor de chaves auto-hospedado. Já não é necessária uma palavra-passe mestra para iniciar sessão para os membros desta organização.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Já não é necessária uma palavra-passe mestra para os membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." + }, + "organizationName": { + "message": "Nome da organização" + }, + "keyConnectorDomain": { + "message": "Domínio do Key Connector" }, "leaveOrganization": { "message": "Sair da organização" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Alterar palavra-passe em risco" }, + "cannotRemoveViewOnlyCollections": { + "message": "Não é possível remover coleções com permissões de Apenas visualização: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Mover" }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 9024aaa5533..f108d420829 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Parola principală înlăturată" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ folosește SSO cu un server de chei auto-găzduit. Membrii acestei organizații nu mai au nevoie de o parolă principală pentru autentificare.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Părăsire organizație" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 0e58487ae88..52dea4d205c 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -157,7 +157,7 @@ "message": "Код безопасности" }, "identityName": { - "message": "Название личности" + "message": "Название личной информации" }, "company": { "message": "Компания" @@ -2188,7 +2188,7 @@ "message": "Импорт элементов в ваше личное хранилище отключен политикой организации." }, "personalDetails": { - "message": "Личные данные" + "message": "Личная информация" }, "identification": { "message": "Идентификация" @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Мастер-пароль удален." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ использует SSO с собственным сервером ключей. Для авторизации пользователям этой организации больше не требуется мастер-пароль.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Мастер-пароль больше не требуется для членов следующей организации. Пожалуйста, подтвердите указанный ниже домен у администратора вашей организации." + }, + "organizationName": { + "message": "Название организации" + }, + "keyConnectorDomain": { + "message": "Домен соединителя ключей" }, "leaveOrganization": { "message": "Покинуть организацию" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Изменить пароль, подверженный риску" }, + "cannotRemoveViewOnlyCollections": { + "message": "Вы не можете удалить коллекции с правами только на просмотр: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Переместить" }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index bb843718e28..03124c345da 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index d84b164e749..3ee4a800a61 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hlavné heslo bolo odstránené." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používa SSO s vlastným kľúčovým serverom. Na prihlásenie členov tejto organizácie už nie je potrebné hlavné heslo.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavné heslo sa už nevyžaduje pre členov tejto organizácie. Nižšie uvedenú doménu potvrďte u správcu organizácie." + }, + "organizationName": { + "message": "Názov organizácie" + }, + "keyConnectorDomain": { + "message": "Doména Key Connectora" }, "leaveOrganization": { "message": "Opustiť organizáciu" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Zmeniť rizikové heslá" }, + "cannotRemoveViewOnlyCollections": { + "message": "Zbierky, ktoré môžete len zobraziť nemôžete odstrániť: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Presunúť" }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 6cc9e6fc197..e1f7dcd578b 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Glavno geslo je bilo odstranjeno." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Zapusti organizacijo" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 7509870b0a0..d13c28ae4da 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -238,22 +238,22 @@ "message": "SSH агент је услуга намењена програмерима која вам омогућава да потпишете SSH захтеве директно из вашег Bitwarden сефа." }, "sshAgentPromptBehavior": { - "message": "Ask for authorization when using SSH agent" + "message": "Затражите ауторизацију када користите SSH агент" }, "sshAgentPromptBehaviorDesc": { - "message": "Choose how to handle SSH-agent authorization requests." + "message": "Изаберите како поднети захтеве за ауторизацију SSH-агента." }, "sshAgentPromptBehaviorHelp": { - "message": "Remember SSH authorizations" + "message": "Запамти SSH дозволе" }, "sshAgentPromptBehaviorAlways": { - "message": "Always" + "message": "Увек" }, "sshAgentPromptBehaviorNever": { - "message": "Never" + "message": "Никада" }, "sshAgentPromptBehaviorRememberUntilLock": { - "message": "Remember until vault is locked" + "message": "Запамти док сеф није блокиран" }, "premiumRequired": { "message": "Потребан Премијум" @@ -446,10 +446,10 @@ "message": "Додај поље" }, "editField": { - "message": "Edit field" + "message": "Уреди поље" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Да ли сте сигурни да желите да трајно избришете овај прилог?" }, "fieldType": { "message": "Врста поља" @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Главна лозинка уклоњена" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ користи SSO уз сопствени сервер за кључеве. Главна лозинка за пријаву више није неопходна за чланове ове организације.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Главна лозинка више није потребна за чланове следеће организације. Молимо потврдите домен са администратором организације." + }, + "organizationName": { + "message": "Назив организације" + }, + "keyConnectorDomain": { + "message": "Домен конектора кључа" }, "leaveOrganization": { "message": "Напусти организацију" @@ -3614,25 +3614,25 @@ "message": "Биометријско откључавање није доступно из непознатог разлога." }, "itemDetails": { - "message": "Item details" + "message": "Детаљи ставке" }, "itemName": { - "message": "Item name" + "message": "Име ставке" }, "loginCredentials": { - "message": "Login credentials" + "message": "Акредитиве за пријављивање" }, "additionalOptions": { - "message": "Additional options" + "message": "Додатне опције" }, "itemHistory": { - "message": "Item history" + "message": "Историја предмета" }, "lastEdited": { - "message": "Last edited" + "message": "Последња измена" }, "upload": { - "message": "Upload" + "message": "Отпреми" }, "authorize": { "message": "Ауторизуј" @@ -3703,33 +3703,42 @@ "changeAtRiskPassword": { "message": "Променити ризичну лозинку" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Премести" }, "newFolder": { - "message": "New folder" + "message": "Нова фасцикла" }, "folderName": { - "message": "Folder Name" + "message": "Име фасцикле" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Угнездите фасциклу додавањем имена надређене фасцкле праћеног знаком „/“. Пример: Друштвени/Форуми" }, "newLoginNudgeTitle": { "message": "Уштедите време са ауто-пуњењем" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "Укључите", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Веб сајт", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "тако да се ова пријава појављује као предлог за ауто-пуњење.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -3755,12 +3764,12 @@ "message": "Лак SSH приступ" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "Чувајте кључеве и повежите се са SSH агент за брзу, шифровану аутентификацију.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "Сазнајте више о SSH агенту", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index eb0d2dcd547..e4fbc6f5773 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Huvudlösenord togs bort" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ använder SSO med en egen nyckelserver. Ett huvudlösenord krävs inte längre för att logga in för medlemmar i denna organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lämna organisation" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 471d7235945..17495f96785 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 43aaa697a8a..34669ba21c4 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index c6a59afda23..43a134abfb0 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Ana parola kaldırıldı" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kendi barındırdığı bir anahtar sunucusuyla SSO kullanıyor. Bu kuruluşun üyelerinin artık ana parola kullanması gerekmiyor.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Kuruluş adı" + }, + "keyConnectorDomain": { + "message": "Key Connector alan adı" }, "leaveOrganization": { "message": "Kuruluştan ayrıl" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Taşı" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 156c8610ba2..a4a63da8089 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Головний пароль вилучено" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ використовує SSO з власним сервером ключів. Головний пароль для учасників цієї організації більше не вимагається.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Покинути організацію" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Змінити ризикований пароль" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Перемістити" }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 2fd354ed08b..4ab9257691c 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -27,7 +27,7 @@ "message": "Ghi chú bảo mật" }, "typeSshKey": { - "message": "SSH key" + "message": "Khóa SSH" }, "folders": { "message": "Thư mục" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Chào mừng bạn trở lại" }, "moveToOrgDesc": { "message": "Chọn một tổ chức mà bạn muốn chuyển mục này đến. Việc chuyển đến một tổ chức sẽ chuyển quyền sở hữu mục này cho tổ chức đó. Bạn sẽ không còn là chủ sở hữu trực tiếp của mục này khi đã chuyển." @@ -181,16 +181,16 @@ "message": "Địa chỉ" }, "sshPrivateKey": { - "message": "Private key" + "message": "Khóa riêng SSH" }, "sshPublicKey": { - "message": "Public key" + "message": "Khóa công khai SSH" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Mã vân tay SSH" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Loại thuật toán" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -205,55 +205,55 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "Khóa SSH mới đã được tạo" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "Mật khẩu bạn đã nhập không chính xác." }, "importSshKey": { - "message": "Import" + "message": "Nhập khóa" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Xác nhận mật khẩu" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Nhập mật khẩu cho khóa SSH." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Nhập mật khẩu" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "Vui lòng mở khóa kho lưu trữ của bạn để phê duyệt yêu cầu khóa SSH." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "Hết thời gian chờ yêu cầu khóa SSH." }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "Bật SSH agent" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "Bật SSH agent để ký các yêu cầu SSH ngay từ kho lưu trữ Bitwarden của bạn." }, "enableSshAgentHelp": { - "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + "message": "SSH agent là một dịch vụ hướng tới các nhà phát triển, cho phép bạn ký các yêu cầu SSH trực tiếp từ kho lưu trữ Bitwarden của mình." }, "sshAgentPromptBehavior": { - "message": "Ask for authorization when using SSH agent" + "message": "Yêu cầu cấp quyền khi sử dụng SSH agent" }, "sshAgentPromptBehaviorDesc": { - "message": "Choose how to handle SSH-agent authorization requests." + "message": "Chọn cách xử lý các yêu cầu cấp quyền của SSH-agent." }, "sshAgentPromptBehaviorHelp": { - "message": "Remember SSH authorizations" + "message": "Ghi nhớ các quyền SSH" }, "sshAgentPromptBehaviorAlways": { - "message": "Always" + "message": "Luôn luôn" }, "sshAgentPromptBehaviorNever": { - "message": "Never" + "message": "Không bao giờ" }, "sshAgentPromptBehaviorRememberUntilLock": { - "message": "Remember until vault is locked" + "message": "Ghi nhớ cho đến khi kho lưu trữ bị khóa" }, "premiumRequired": { "message": "Cần có tài khoản cao cấp" @@ -268,17 +268,17 @@ "message": "Lỗi" }, "decryptionError": { - "message": "Decryption error" + "message": "Lỗi giải mã" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden không thể giải mã các kho lưu trữ được liệt kê dưới đây." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "Liên hệ hỗ trợ khách hàng thành công", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "để tránh mất mát thêm dữ liệu.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "january": { @@ -355,7 +355,7 @@ "message": "Tạo mật khẩu" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Tạo cụm mật khẩu" }, "type": { "message": "Loại" @@ -412,16 +412,16 @@ "message": "Khóa xác thực (TOTP)" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Khóa xác thực" }, "autofillOptions": { - "message": "Autofill options" + "message": "Tùy chọn tự động điền" }, "websiteUri": { - "message": "Website (URI)" + "message": "Trang web (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "Trang web (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -431,46 +431,46 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Đã thêm trang web" }, "addWebsite": { - "message": "Add website" + "message": "Thêm trang web" }, "deleteWebsite": { - "message": "Delete website" + "message": "Xóa trang web" }, "owner": { - "message": "Owner" + "message": "Chủ sở hữu" }, "addField": { - "message": "Add field" + "message": "Thêm trường" }, "editField": { - "message": "Edit field" + "message": "Chỉnh sửa trường" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Bạn có chắc muốn xóa vĩnh viễn tệp đính kèm này không?" }, "fieldType": { - "message": "Field type" + "message": "Loại trường" }, "fieldLabel": { - "message": "Field label" + "message": "Nhãn trường" }, "add": { - "message": "Add" + "message": "Thêm" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "Sử dụng các trường văn bản cho dữ liệu như câu hỏi bảo mật" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "Sử dụng trường ẩn cho dữ liệu nhạy cảm như mật khẩu" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "Dùng các ô tích chọn nếu bạn muốn tự động điền vào ô tích chọn của biểu mẫu, chẳng hạn như ghi nhớ email" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "Sử dụng trường liên kết khi bạn gặp sự cố tự động điền trên một trang web cụ thể." }, "linkedLabelHelpText": { "message": "Enter the the field's html id, name, aria-label, or placeholder." @@ -1184,7 +1184,7 @@ "message": "Unlock with biometrics" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Mở khóa bằng mật khẩu chính" }, "unlock": { "message": "Mở khóa" @@ -1508,13 +1508,13 @@ "message": "Chưa có mật khẩu." }, "clearHistory": { - "message": "Clear history" + "message": "Xóa lịch sử" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Không có gì để hiển thị" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Bạn chưa tạo gì gần đây" }, "undo": { "message": "Hoàn tác" @@ -1591,7 +1591,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "Sao chép thành công" }, "errorRefreshingAccessToken": { "message": "Lỗi làm mới khoá truy cập" @@ -1824,13 +1824,13 @@ "message": "Yêu cầu mật khẩu hoặc mã PIN khi mở" }, "requirePasswordWithoutPinOnStart": { - "message": "Require password on app start" + "message": "Yêu cầu mật khẩu khi khởi động ứng dụng" }, "recommendedForSecurity": { "message": "Đề xuất để tăng cường bảo mật." }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "Khóa bằng mật khẩu chính khi khởi động lại" }, "deleteAccount": { "message": "Xóa tài khoản" @@ -1842,10 +1842,10 @@ "message": "Việc xóa tài khoản là vĩnh viễn và không thể hoàn tác." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "Không thể xóa tài khoản" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "Không thể thực hiện hành động này vì tài khoản của bạn thuộc sở hữu của một tổ chức. Liên hệ với quản trị viên tổ chức của bạn để biết thêm chi tiết." }, "accountDeleted": { "message": "Đã xóa tài khoản" @@ -1958,7 +1958,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "trong tổng số $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1967,10 +1967,10 @@ } }, "cardDetails": { - "message": "Card details" + "message": "Thông tin thẻ" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Chi tiết của $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -1979,32 +1979,32 @@ } }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Tìm hiểu thêm về các công cụ xác thực" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Sao chép khóa xác thực (TOTP)" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Làm cho xác thực 2 bước trở nên liền mạch" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden có thể lưu trữ và điền mã xác thực 2 bước. Hãy sao chép và dán khóa vào trường này." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden có thể lưu trữ và điền mã xác thực 2 bước. Hãy chọn biểu tượng máy ảnh để chụp mã QR xác thực trên trang web này, hoặc sao chép và dán khóa vào trường này." }, "premium": { - "message": "Premium", + "message": "Phiên bản cao cấp", "description": "Premium membership" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "Các tổ chức miễn phí không thể sử dụng tệp đính kèm" }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 trường cần bạn chú ý." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ trường cần bạn chú ý.", "placeholders": { "count": { "content": "$1", @@ -2013,10 +2013,10 @@ } }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Thẻ hết hạn" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "Nếu bạn đã gia hạn, hãy cập nhật thông tin thẻ" }, "verificationRequired": { "message": "Yêu cầu xác minh", @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Đã xóa mật khẩu chính" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ đang sử dụng SSO với khóa máy chủ tự lưu trữ. Mật khẩu chính không còn cần để đăng nhập cho các thành viên của tổ chức này.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Rời khỏi tổ chức" @@ -2606,7 +2606,7 @@ "message": "Đã khóa" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Kho lưu trữ đã bị khóa" }, "unlocked": { "message": "Đã mở khóa" @@ -3067,7 +3067,7 @@ "message": "Quan trọng:" }, "accessing": { - "message": "Accessing" + "message": "Đang truy cập" }, "accessTokenUnableToBeDecrypted": { "message": "Bạn đã bị đăng xuất do khoá truy cập của bạn không thể giải mã. Vui lòng đăng nhập lại để khắc phục sự cố này." @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 90cdb33fde5..69d5faa5fb7 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1836,7 +1836,7 @@ "message": "删除账户" }, "deleteAccountDesc": { - "message": "接下来的操作会删除您的账户和所有密码库数据。" + "message": "继续下面的操作以删除您的账户和所有密码库数据。" }, "deleteAccountWarning": { "message": "删除账户是永久性操作,无法撤销!" @@ -2363,7 +2363,7 @@ "message": "读取安全密钥" }, "awaitingSecurityKeyInteraction": { - "message": "等待安全密钥交互……" + "message": "等待安全密钥交互..." }, "hideEmail": { "message": "对接收者隐藏我的电子邮箱地址。" @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "主密码已移除" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自托管密钥服务器 SSO。这个组织的成员登录时将不再需要主密码。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "以下组织的成员不再需要主密码。请与您的组织管理员确认下面的域名。" + }, + "organizationName": { + "message": "组织名称" + }, + "keyConnectorDomain": { + "message": "Key Connector 域名" }, "leaveOrganization": { "message": "退出组织" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "更改有风险的密码" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "移动" }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index eeeaf9b152e..6080c3ee0f8 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "主密碼已移除" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自我裝載金鑰伺服器 SSO。此組織的成員登入時將不再需要主密碼。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "離開組織" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, From 56a3b14583747a499d66960ac5b372ee0857246e Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Fri, 23 May 2025 08:01:25 -0400 Subject: [PATCH 11/41] Introduce eslint errors for risky/circular imports (#14804) * first draft at an idea dependency graph * ignore existing errors * remove conflicting rule regarding internal platform logic in libs * review: allow components to import from platform --- .../messaging/chrome-message.sender.ts | 2 + .../platform/sync/foreground-sync.service.ts | 2 + .../sync/sync-service.listener.spec.ts | 2 + .../utils/from-chrome-runtime-messaging.ts | 2 + .../electron-renderer-message.sender.ts | 2 + .../src/platform/utils/from-ipc-messaging.ts | 2 + eslint.config.mjs | 248 +++++++++++++++++- .../components/collections.component.ts | 2 + .../base-login-via-webauthn.component.ts | 2 + .../environment-selector.component.ts | 2 + .../auth/components/set-password.component.ts | 4 + .../src/auth/components/set-pin.component.ts | 2 + .../src/auth/guards/active-auth.guard.spec.ts | 2 + .../src/auth/guards/active-auth.guard.ts | 2 + ...vice-trust-toast.service.implementation.ts | 2 + .../device-trust-toast.service.spec.ts | 2 + .../angular/src/components/share.component.ts | 2 + libs/angular/src/services/injection-tokens.ts | 2 + .../src/services/jslib-services.module.ts | 8 + .../view-password-history.service.spec.ts | 2 + .../services/view-password-history.service.ts | 2 + .../deprecated-vault-filter.service.ts | 2 + .../vault/components/add-edit.component.ts | 4 + .../src/vault/components/view.component.ts | 2 + .../account-security-nudge.service.ts | 2 + .../empty-vault-nudge.service.ts | 2 + .../src/vault/services/nudges.service.spec.ts | 2 + .../components/collection-filter.component.ts | 2 + .../components/vault-filter.component.ts | 2 + .../services/vault-filter.service.ts | 2 + .../anon-layout-wrapper.component.ts | 2 + .../anon-layout-wrapper.stories.ts | 2 + .../change-password.component.ts | 2 + .../fingerprint-dialog.component.ts | 2 + .../src/angular/icons/bitwarden-logo.icon.ts | 2 + .../angular/icons/bitwarden-shield.icon.ts | 2 + .../angular/icons/device-verification.icon.ts | 2 + libs/auth/src/angular/icons/devices.icon.ts | 2 + libs/auth/src/angular/icons/lock.icon.ts | 2 + .../icons/registration-check-email.icon.ts | 2 + .../icons/registration-expired-link.icon.ts | 2 + .../icons/registration-lock-alt.icon.ts | 2 + .../icons/registration-user-add.icon.ts | 2 + libs/auth/src/angular/icons/sso-key.icon.ts | 2 + .../two-factor-auth-authenticator.icon.ts | 2 + .../two-factor-auth-duo.icon.ts | 2 + .../two-factor-auth-email.icon.ts | 2 + .../two-factor-auth-security-key.icon.ts | 2 + .../two-factor-auth-webauthn.icon.ts | 2 + .../two-factor-auth-yubico.icon.ts | 2 + .../angular/icons/two-factor-timeout.icon.ts | 2 + libs/auth/src/angular/icons/user-lock.icon.ts | 2 + ...erification-biometrics-fingerprint.icon.ts | 2 + libs/auth/src/angular/icons/vault.icon.ts | 2 + libs/auth/src/angular/icons/wave.icon.ts | 2 + .../input-password.component.ts | 2 + .../input-password/input-password.stories.ts | 2 + .../login-approval.component.spec.ts | 2 + .../login-approval.component.ts | 2 + .../login-decryption-options.component.ts | 2 + .../login-via-auth-request.component.ts | 2 + .../login-secondary-content.component.ts | 2 + .../auth/src/angular/login/login.component.ts | 2 + .../new-device-verification.component.ts | 2 + .../password-callout.component.ts | 2 + .../password-callout.stories.ts | 2 + .../password-hint/password-hint.component.ts | 2 + .../registration-env-selector.component.ts | 2 + .../registration-finish.component.ts | 2 + .../registration-link-expired.component.ts | 2 + .../registration-start-secondary.component.ts | 2 + .../registration-start.component.ts | 2 + .../registration-start.stories.ts | 2 + ...self-hosted-env-config-dialog.component.ts | 2 + .../default-set-password-jit.service.spec.ts | 2 + .../default-set-password-jit.service.ts | 2 + libs/auth/src/angular/sso/sso.component.ts | 2 + ...two-factor-auth-authenticator.component.ts | 2 + .../two-factor-auth-duo.component.ts | 2 + .../two-factor-auth-email.component.ts | 2 + .../two-factor-auth-webauthn.component.ts | 2 + .../two-factor-auth-yubikey.component.ts | 2 + .../two-factor-auth.component.spec.ts | 2 + .../two-factor-auth.component.ts | 2 + .../two-factor-options.component.ts | 2 + .../user-verification-dialog.component.ts | 2 + .../user-verification-dialog.types.ts | 2 + .../user-verification-form-input.component.ts | 2 + .../vault-timeout-input.component.ts | 2 + libs/common/src/abstractions/api.service.ts | 2 + .../response/organization-export.response.ts | 2 + .../provider-user-bulk-public-key.response.ts | 2 + .../registration/register-finish.request.ts | 2 + .../models/request/set-password.request.ts | 2 + ...update-tde-offboarding-password.request.ts | 2 + .../request/update-temp-password.request.ts | 2 + .../response/identity-token.response.ts | 2 + .../auth/models/response/prelogin.response.ts | 2 + .../response/protected-device.response.ts | 2 + .../src/auth/services/auth.service.spec.ts | 2 + libs/common/src/auth/services/auth.service.ts | 2 + .../master-password-api.service.spec.ts | 2 + ...-enrollment.service.implementation.spec.ts | 4 + ...reset-enrollment.service.implementation.ts | 4 + .../src/auth/services/token.service.spec.ts | 2 + .../common/src/auth/services/token.service.ts | 2 + .../user-verification.service.spec.ts | 4 + .../user-verification.service.ts | 4 + .../webauthn-login.service.spec.ts | 2 + .../webauthn-login/webauthn-login.service.ts | 2 + libs/common/src/auth/types/verification.ts | 2 + .../organization-billing.service.spec.ts | 2 + .../services/organization-billing.service.ts | 2 + .../device-trust.service.implementation.ts | 4 + .../services/device-trust.service.spec.ts | 4 + .../models/set-key-connector-key.request.ts | 2 + .../services/key-connector.service.spec.ts | 2 + .../services/key-connector.service.ts | 4 + .../default-process-reload.service.ts | 4 + .../vault-timeout-settings.service.spec.ts | 4 + .../vault-timeout-settings.service.ts | 4 + .../services/vault-timeout.service.spec.ts | 6 + .../services/vault-timeout.service.ts | 6 + .../export/collection-with-id.export.ts | 2 + .../src/models/export/collection.export.ts | 2 + .../import-organization-ciphers.request.ts | 2 + libs/common/src/models/request/kdf.request.ts | 2 + .../abstractions/key-generation.service.ts | 2 + libs/common/src/platform/misc/utils.ts | 2 + .../platform/models/domain/enc-string.spec.ts | 2 + .../default-notifications.service.spec.ts | 2 + .../internal/default-notifications.service.ts | 2 + .../platform/services/container.service.ts | 2 + .../services/key-generation.service.spec.ts | 2 + .../services/key-generation.service.ts | 2 + .../services/sdk/default-sdk.service.spec.ts | 2 + .../services/sdk/default-sdk.service.ts | 2 + .../user-auto-unlock-key.service.spec.ts | 2 + .../services/user-auto-unlock-key.service.ts | 2 + .../src/platform/sync/core-sync.service.ts | 2 + .../sync/default-sync.service.spec.ts | 6 + .../src/platform/sync/default-sync.service.ts | 4 + .../common/src/platform/sync/sync.response.ts | 2 + libs/common/src/services/api.service.spec.ts | 2 + libs/common/src/services/api.service.ts | 4 + ...-service-legacy-encryptor-provider.spec.ts | 2 + .../key-service-legacy-encryptor-provider.ts | 2 + .../src/tools/send/models/domain/send.spec.ts | 2 + .../send/services/send.service.abstraction.ts | 2 + .../tools/send/services/send.service.spec.ts | 2 + .../src/tools/send/services/send.service.ts | 2 + .../src/vault/abstractions/cipher.service.ts | 2 + .../folder/folder.service.abstraction.ts | 2 + .../vault/models/domain/attachment.spec.ts | 2 + .../src/vault/models/domain/cipher.spec.ts | 2 + .../cipher-authorization.service.spec.ts | 2 + .../services/cipher-authorization.service.ts | 2 + .../src/vault/services/cipher.service.spec.ts | 2 + .../src/vault/services/cipher.service.ts | 2 + .../services/folder/folder.service.spec.ts | 2 + .../vault/services/folder/folder.service.ts | 2 + .../src/components/import.component.ts | 2 + libs/importer/src/importers/base-importer.ts | 2 + .../bitwarden/bitwarden-json-importer.ts | 2 + .../src/importers/padlock-csv-importer.ts | 2 + .../src/importers/passpack-csv-importer.ts | 2 + libs/importer/src/models/import-result.ts | 2 + .../import-collection.service.abstraction.ts | 2 + .../services/import.service.abstraction.ts | 2 + .../src/services/import.service.spec.ts | 2 + libs/importer/src/services/import.service.ts | 2 + libs/key-management/src/key.service.spec.ts | 2 + libs/key-management/src/key.service.ts | 2 + .../cipher-form-config.service.ts | 2 + .../src/cipher-form/cipher-form.stories.ts | 2 + .../item-details-section.component.spec.ts | 2 + .../item-details-section.component.ts | 2 + .../default-cipher-form-config.service.ts | 2 + .../src/cipher-view/cipher-view.component.ts | 2 + .../item-details-v2.component.spec.ts | 2 + .../item-details/item-details-v2.component.ts | 2 + .../assign-collections.component.ts | 2 + 182 files changed, 652 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/platform/messaging/chrome-message.sender.ts b/apps/browser/src/platform/messaging/chrome-message.sender.ts index 4fc9c22e26b..c0736f2456e 100644 --- a/apps/browser/src/platform/messaging/chrome-message.sender.ts +++ b/apps/browser/src/platform/messaging/chrome-message.sender.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CommandDefinition, MessageSender } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { getCommand } from "@bitwarden/common/platform/messaging/internal"; type ErrorHandler = (logger: LogService, command: string) => void; diff --git a/apps/browser/src/platform/sync/foreground-sync.service.ts b/apps/browser/src/platform/sync/foreground-sync.service.ts index ce776f53685..2ac75bbec2c 100644 --- a/apps/browser/src/platform/sync/foreground-sync.service.ts +++ b/apps/browser/src/platform/sync/foreground-sync.service.ts @@ -13,6 +13,8 @@ import { } from "@bitwarden/common/platform/messaging"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CoreSyncService } from "@bitwarden/common/platform/sync/internal"; import { SyncOptions } from "@bitwarden/common/platform/sync/sync.service"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; diff --git a/apps/browser/src/platform/sync/sync-service.listener.spec.ts b/apps/browser/src/platform/sync/sync-service.listener.spec.ts index 9682e2cdb57..dc0674a7ae5 100644 --- a/apps/browser/src/platform/sync/sync-service.listener.spec.ts +++ b/apps/browser/src/platform/sync/sync-service.listener.spec.ts @@ -3,6 +3,8 @@ import { Subject, firstValueFrom } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { tagAsExternal } from "@bitwarden/common/platform/messaging/helpers"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; diff --git a/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts b/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts index ebc01ad86fa..9489c5f2a4e 100644 --- a/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts +++ b/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts @@ -1,6 +1,8 @@ import { map, share } from "rxjs"; import { Message } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal"; import { fromChromeEvent } from "../browser/from-chrome-event"; diff --git a/apps/desktop/src/platform/services/electron-renderer-message.sender.ts b/apps/desktop/src/platform/services/electron-renderer-message.sender.ts index 109a52a8d8f..a70542e7b20 100644 --- a/apps/desktop/src/platform/services/electron-renderer-message.sender.ts +++ b/apps/desktop/src/platform/services/electron-renderer-message.sender.ts @@ -1,4 +1,6 @@ import { MessageSender, CommandDefinition } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { getCommand } from "@bitwarden/common/platform/messaging/internal"; export class ElectronRendererMessageSender implements MessageSender { diff --git a/apps/desktop/src/platform/utils/from-ipc-messaging.ts b/apps/desktop/src/platform/utils/from-ipc-messaging.ts index cdefbf5c506..f9c01c9487f 100644 --- a/apps/desktop/src/platform/utils/from-ipc-messaging.ts +++ b/apps/desktop/src/platform/utils/from-ipc-messaging.ts @@ -1,6 +1,8 @@ import { fromEventPattern, share } from "rxjs"; import { Message } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal"; /** diff --git a/eslint.config.mjs b/eslint.config.mjs index 7928224dc00..aa0b475da0b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -278,14 +278,254 @@ export default tseslint.config( ]), }, }, - - /// Team overrides + /// Bandaids for keeping existing circular dependencies from getting worse and new ones from being created + /// Will be removed after Nx is implemented and existing circular dependencies are removed. { - files: ["**/src/platform/**/*.ts"], + files: ["libs/common/src/**/*.ts"], rules: { - "no-restricted-imports": buildNoRestrictedImports([], true), + "no-restricted-imports": buildNoRestrictedImports([ + // Common is at the base level - should not import from other libs except shared + "@bitwarden/admin-console", + "@bitwarden/angular", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/components", + "@bitwarden/importer", + "@bitwarden/key-management", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/ui", + "@bitwarden/vault", + ]), }, }, + { + files: ["libs/shared/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Shared shouldnt have deps + "@bitwarden/admin-console", + "@bitwarden/angular", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/common", + "@bitwarden/components", + "@bitwarden/importer", + "@bitwarden/key-management", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/ui", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/auth/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Auth can only depend on common, shared, angular, node, platform, eslint + "@bitwarden/admin-console", + "@bitwarden/billing", + "@bitwarden/components", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/tools", + "@bitwarden/ui", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/key-management/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Key management can depend on common, node, angular, components, eslint, platform, ui + "@bitwarden/auth", + "@bitwarden/admin-console", + "@bitwarden/billing", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/billing/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Billing can depend on auth, common, angular, components, eslint, node, platform, ui + "@bitwarden/admin-console", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/components/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Components can depend on common, shared + "@bitwarden/admin-console", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/eslint", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/ui/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // UI can depend on common, shared, auth + "@bitwarden/admin-console", + "@bitwarden/billing", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/key-management-ui/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Key-management-ui can depend on key-management, common, angular, shared, auth, components, ui, eslint + "@bitwarden/admin-console", + "@bitwarden/billing", + "@bitwarden/importer", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/angular/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Angular can depend on common, shared, components, ui + "@bitwarden/admin-console", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/vault/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Vault can depend on most libs + "@bitwarden/admin-console", + "@bitwarden/importer", + "@bitwarden/tools", + ]), + }, + }, + { + files: ["libs/admin-console/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Admin console can depend on all libs + ]), + }, + }, + { + files: ["libs/tools/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Tools can depend on most libs + "@bitwarden/admin-console", + ]), + }, + }, + { + files: ["libs/platform/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Platform cant depend on most libs + "@bitwarden/admin-console", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/importer", + "@bitwarden/key-management", + "@bitwarden/key-management-ui", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/importer/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Importer can depend on most libs but not other domain libs + "@bitwarden/admin-console", + "@bitwarden/tools", + ]), + }, + }, + { + files: ["libs/eslint/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // ESLint should not depend on app code + "@bitwarden/admin-console", + "@bitwarden/angular", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/components", + "@bitwarden/importer", + "@bitwarden/key-management", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/ui", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/node/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Node can depend on common, shared, auth + "@bitwarden/admin-console", + "@bitwarden/angular", + "@bitwarden/components", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/ui", + "@bitwarden/vault", + ]), + }, + }, + + /// Team overrides { files: [ "apps/cli/src/admin-console/**/*.ts", diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index 8ae90705f92..9d36ab4619f 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -3,6 +3,8 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; diff --git a/libs/angular/src/auth/components/base-login-via-webauthn.component.ts b/libs/angular/src/auth/components/base-login-via-webauthn.component.ts index 5d30fc997dc..53e29d4d940 100644 --- a/libs/angular/src/auth/components/base-login-via-webauthn.component.ts +++ b/libs/angular/src/auth/components/base-login-via-webauthn.component.ts @@ -4,6 +4,8 @@ import { Directive, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LoginSuccessHandlerService } from "@bitwarden/auth/common"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginCredentialAssertionView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion.view"; diff --git a/libs/angular/src/auth/components/environment-selector.component.ts b/libs/angular/src/auth/components/environment-selector.component.ts index b61feedd306..1831e513301 100644 --- a/libs/angular/src/auth/components/environment-selector.component.ts +++ b/libs/angular/src/auth/components/environment-selector.component.ts @@ -4,6 +4,8 @@ import { Component, EventEmitter, Output, Input, OnInit, OnDestroy } from "@angu import { ActivatedRoute } from "@angular/router"; import { Observable, map, Subject, takeUntil } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { SelfHostedEnvConfigDialogComponent } from "@bitwarden/auth/angular"; import { EnvironmentService, diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts index 230be90b7a4..53f6abaa33c 100644 --- a/libs/angular/src/auth/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -5,10 +5,14 @@ import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, of } from "rxjs"; import { filter, first, switchMap, tap } from "rxjs/operators"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserApiService, OrganizationUserResetPasswordEnrollmentRequest, } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; diff --git a/libs/angular/src/auth/components/set-pin.component.ts b/libs/angular/src/auth/components/set-pin.component.ts index d2fceb34bb9..fba6ce2da86 100644 --- a/libs/angular/src/auth/components/set-pin.component.ts +++ b/libs/angular/src/auth/components/set-pin.component.ts @@ -4,6 +4,8 @@ import { Directive, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; diff --git a/libs/angular/src/auth/guards/active-auth.guard.spec.ts b/libs/angular/src/auth/guards/active-auth.guard.spec.ts index c3417b9d41d..566b2abc72a 100644 --- a/libs/angular/src/auth/guards/active-auth.guard.spec.ts +++ b/libs/angular/src/auth/guards/active-auth.guard.spec.ts @@ -5,6 +5,8 @@ import { RouterTestingModule } from "@angular/router/testing"; import { MockProxy, mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/libs/angular/src/auth/guards/active-auth.guard.ts b/libs/angular/src/auth/guards/active-auth.guard.ts index 56213bbd979..83c648ae110 100644 --- a/libs/angular/src/auth/guards/active-auth.guard.ts +++ b/libs/angular/src/auth/guards/active-auth.guard.ts @@ -2,6 +2,8 @@ import { inject } from "@angular/core"; import { CanActivateFn, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts b/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts index 4013cf8df96..31e90548a7a 100644 --- a/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts +++ b/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts @@ -1,5 +1,7 @@ import { merge, Observable, tap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/libs/angular/src/auth/services/device-trust-toast.service.spec.ts b/libs/angular/src/auth/services/device-trust-toast.service.spec.ts index 96b7db9d0ce..10ad3203b90 100644 --- a/libs/angular/src/auth/services/device-trust-toast.service.spec.ts +++ b/libs/angular/src/auth/services/device-trust-toast.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { EMPTY, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index 198cc7dc3a5..51827bfb9f2 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -3,6 +3,8 @@ import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index d82ff021962..4c29abe680a 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable, Subject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { ClientType } from "@bitwarden/common/enums"; import { VaultTimeout } from "@bitwarden/common/key-management/vault-timeout"; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index a8638efba18..08bcaa2165c 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -3,12 +3,16 @@ import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core"; import { Subject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, DefaultCollectionService, DefaultOrganizationUserApiService, OrganizationUserApiService, } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AnonLayoutWrapperDataService, DefaultAnonLayoutWrapperDataService, @@ -30,6 +34,8 @@ import { ChangePasswordService, DefaultChangePasswordService, } from "@bitwarden/auth/angular"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AuthRequestApiService, AuthRequestService, @@ -316,6 +322,8 @@ import { UserAsymmetricKeysRegenerationService, } from "@bitwarden/key-management"; import { SafeInjectionToken } from "@bitwarden/ui-common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PasswordRepromptService } from "@bitwarden/vault"; import { IndividualVaultExportService, diff --git a/libs/angular/src/services/view-password-history.service.spec.ts b/libs/angular/src/services/view-password-history.service.spec.ts index dec2b25b190..121aa7dc472 100644 --- a/libs/angular/src/services/view-password-history.service.spec.ts +++ b/libs/angular/src/services/view-password-history.service.spec.ts @@ -3,6 +3,8 @@ import { TestBed } from "@angular/core/testing"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { openPasswordHistoryDialog } from "@bitwarden/vault"; import { VaultViewPasswordHistoryService } from "./view-password-history.service"; diff --git a/libs/angular/src/services/view-password-history.service.ts b/libs/angular/src/services/view-password-history.service.ts index 88ca4d37287..ab4d6d4ddf1 100644 --- a/libs/angular/src/services/view-password-history.service.ts +++ b/libs/angular/src/services/view-password-history.service.ts @@ -3,6 +3,8 @@ import { Injectable } from "@angular/core"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { openPasswordHistoryDialog } from "@bitwarden/vault"; /** diff --git a/libs/angular/src/vault/abstractions/deprecated-vault-filter.service.ts b/libs/angular/src/vault/abstractions/deprecated-vault-filter.service.ts index 0a7d6397a04..3e82641fe90 100644 --- a/libs/angular/src/vault/abstractions/deprecated-vault-filter.service.ts +++ b/libs/angular/src/vault/abstractions/deprecated-vault-filter.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index b04adc1fdfb..8175372cae5 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -4,6 +4,8 @@ import { DatePipe } from "@angular/common"; import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { concatMap, firstValueFrom, map, Observable, Subject, switchMap, takeUntil } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; @@ -40,6 +42,8 @@ import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { generate_ssh_key } from "@bitwarden/sdk-internal"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault"; @Directive() diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index fd3f92c6c73..ac14c092490 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -54,6 +54,8 @@ import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cip import { TotpInfo } from "@bitwarden/common/vault/services/totp.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PasswordRepromptService } from "@bitwarden/vault"; const BroadcasterSubscriptionId = "BaseViewComponent"; diff --git a/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts index 862beb333d4..30bbd153c5e 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts @@ -3,6 +3,8 @@ import { Observable, combineLatest, from, of } from "rxjs"; import { catchError, map } from "rxjs/operators"; import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts index a0e25aa841c..d579f8b1bb2 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts @@ -1,6 +1,8 @@ import { inject, Injectable } from "@angular/core"; import { combineLatest, Observable, of, switchMap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; diff --git a/libs/angular/src/vault/services/nudges.service.spec.ts b/libs/angular/src/vault/services/nudges.service.spec.ts index 4edd57f5428..30e2ada6007 100644 --- a/libs/angular/src/vault/services/nudges.service.spec.ts +++ b/libs/angular/src/vault/services/nudges.service.spec.ts @@ -2,6 +2,8 @@ import { TestBed } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts b/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts index d104026f2f6..feaaf74c96d 100644 --- a/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Directive, EventEmitter, Input, Output } from "@angular/core"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node"; diff --git a/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts b/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts index 0ce63c03f61..83304f8eae9 100644 --- a/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts @@ -3,6 +3,8 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { firstValueFrom, Observable } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node"; diff --git a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts index 6c3ac21b162..9e3312b38ef 100644 --- a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts +++ b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts @@ -3,6 +3,8 @@ import { Injectable } from "@angular/core"; import { firstValueFrom, from, map, mergeMap, Observable, switchMap, take } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts index 04dc3b6dfd2..17f5ec8e3c6 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts @@ -6,6 +6,8 @@ import { Subject, filter, switchMap, takeUntil, tap } from "rxjs"; import { AnonLayoutComponent } from "@bitwarden/auth/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { Icon, Translation } from "@bitwarden/components"; import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service"; diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts index 9f504c75d29..f106f9ee0dc 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts @@ -15,6 +15,8 @@ import { Environment, } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` diff --git a/libs/auth/src/angular/change-password/change-password.component.ts b/libs/auth/src/angular/change-password/change-password.component.ts index 51c4d03d16f..86b7c884e92 100644 --- a/libs/auth/src/angular/change-password/change-password.component.ts +++ b/libs/auth/src/angular/change-password/change-password.component.ts @@ -8,6 +8,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ToastService } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; diff --git a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts index d8d76930302..17d7b343db9 100644 --- a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts +++ b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts @@ -1,6 +1,8 @@ import { Component, Inject } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DIALOG_DATA, ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; export type FingerprintDialogData = { diff --git a/libs/auth/src/angular/icons/bitwarden-logo.icon.ts b/libs/auth/src/angular/icons/bitwarden-logo.icon.ts index 2a1ae48526b..2df07c45ff9 100644 --- a/libs/auth/src/angular/icons/bitwarden-logo.icon.ts +++ b/libs/auth/src/angular/icons/bitwarden-logo.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const BitwardenLogo = svgIcon` diff --git a/libs/auth/src/angular/icons/bitwarden-shield.icon.ts b/libs/auth/src/angular/icons/bitwarden-shield.icon.ts index 86e3a0bb1b2..f40dc97e5ee 100644 --- a/libs/auth/src/angular/icons/bitwarden-shield.icon.ts +++ b/libs/auth/src/angular/icons/bitwarden-shield.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const BitwardenShield = svgIcon` diff --git a/libs/auth/src/angular/icons/device-verification.icon.ts b/libs/auth/src/angular/icons/device-verification.icon.ts index b1be4efdfb3..6c5313a8705 100644 --- a/libs/auth/src/angular/icons/device-verification.icon.ts +++ b/libs/auth/src/angular/icons/device-verification.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const DeviceVerificationIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/devices.icon.ts b/libs/auth/src/angular/icons/devices.icon.ts index 54acea5b087..dd268f0e6e2 100644 --- a/libs/auth/src/angular/icons/devices.icon.ts +++ b/libs/auth/src/angular/icons/devices.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const DevicesIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/lock.icon.ts b/libs/auth/src/angular/icons/lock.icon.ts index 198733d0dca..43ea2509e19 100644 --- a/libs/auth/src/angular/icons/lock.icon.ts +++ b/libs/auth/src/angular/icons/lock.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const LockIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/registration-check-email.icon.ts b/libs/auth/src/angular/icons/registration-check-email.icon.ts index 6f7dd6a2d63..d32964d8cb1 100644 --- a/libs/auth/src/angular/icons/registration-check-email.icon.ts +++ b/libs/auth/src/angular/icons/registration-check-email.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const RegistrationCheckEmailIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/registration-expired-link.icon.ts b/libs/auth/src/angular/icons/registration-expired-link.icon.ts index 3323c7f0b2b..099cf16d1d8 100644 --- a/libs/auth/src/angular/icons/registration-expired-link.icon.ts +++ b/libs/auth/src/angular/icons/registration-expired-link.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const RegistrationExpiredLinkIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/registration-lock-alt.icon.ts b/libs/auth/src/angular/icons/registration-lock-alt.icon.ts index 511f9710dc6..d312e909413 100644 --- a/libs/auth/src/angular/icons/registration-lock-alt.icon.ts +++ b/libs/auth/src/angular/icons/registration-lock-alt.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const RegistrationLockAltIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/registration-user-add.icon.ts b/libs/auth/src/angular/icons/registration-user-add.icon.ts index 69240cd0298..4f68453639d 100644 --- a/libs/auth/src/angular/icons/registration-user-add.icon.ts +++ b/libs/auth/src/angular/icons/registration-user-add.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const RegistrationUserAddIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/sso-key.icon.ts b/libs/auth/src/angular/icons/sso-key.icon.ts index 38ae8a66525..e00c3555906 100644 --- a/libs/auth/src/angular/icons/sso-key.icon.ts +++ b/libs/auth/src/angular/icons/sso-key.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const SsoKeyIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts index daef1f94dca..4961e7207cb 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthAuthenticatorIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts index c81433d0fc1..a64027c5fba 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthDuoIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts index 833ab3f8e98..380bc16a738 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthEmailIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts index f6ac90cfd5d..573dc890428 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthSecurityKeyIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts index 233533fc807..ff73bf2e255 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthWebAuthnIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts index 6bb989a9d15..8f6dca837ad 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthYubicoIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-timeout.icon.ts b/libs/auth/src/angular/icons/two-factor-timeout.icon.ts index 71d0aa549dc..3681670c124 100644 --- a/libs/auth/src/angular/icons/two-factor-timeout.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-timeout.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorTimeoutIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/user-lock.icon.ts b/libs/auth/src/angular/icons/user-lock.icon.ts index e85eac6fc2d..bc4fdd9e268 100644 --- a/libs/auth/src/angular/icons/user-lock.icon.ts +++ b/libs/auth/src/angular/icons/user-lock.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const UserLockIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/user-verification-biometrics-fingerprint.icon.ts b/libs/auth/src/angular/icons/user-verification-biometrics-fingerprint.icon.ts index f661f9330b1..e329d889574 100644 --- a/libs/auth/src/angular/icons/user-verification-biometrics-fingerprint.icon.ts +++ b/libs/auth/src/angular/icons/user-verification-biometrics-fingerprint.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const UserVerificationBiometricsIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/vault.icon.ts b/libs/auth/src/angular/icons/vault.icon.ts index e23944ab7db..a341bcd99f5 100644 --- a/libs/auth/src/angular/icons/vault.icon.ts +++ b/libs/auth/src/angular/icons/vault.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const VaultIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/wave.icon.ts b/libs/auth/src/angular/icons/wave.icon.ts index 3e4483c1e05..5629c43fc8d 100644 --- a/libs/auth/src/angular/icons/wave.icon.ts +++ b/libs/auth/src/angular/icons/wave.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const WaveIcon = svgIcon` diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index 135a6cfaaa8..8b0b6e4c159 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -17,6 +17,8 @@ import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/input-password/input-password.stories.ts b/libs/auth/src/angular/input-password/input-password.stories.ts index 84ba39e2916..708b74b9925 100644 --- a/libs/auth/src/angular/input-password/input-password.stories.ts +++ b/libs/auth/src/angular/input-password/input-password.stories.ts @@ -14,6 +14,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogService, ToastService } from "@bitwarden/components"; import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management"; diff --git a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts index 5fcc06c8476..afc2c67de12 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts @@ -14,6 +14,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogRef, DIALOG_DATA, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; diff --git a/libs/auth/src/angular/login-approval/login-approval.component.ts b/libs/auth/src/angular/login-approval/login-approval.component.ts index 43d9af64481..784bd93002e 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.ts @@ -17,6 +17,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DIALOG_DATA, DialogRef, diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 3ea9416b7e2..c49e54f8c19 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -26,6 +26,8 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts index d74deb443f5..f23d49f0015 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -34,6 +34,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ButtonModule, LinkModule, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; diff --git a/libs/auth/src/angular/login/login-secondary-content.component.ts b/libs/auth/src/angular/login/login-secondary-content.component.ts index b608542b375..fba5e70d93c 100644 --- a/libs/auth/src/angular/login/login-secondary-content.component.ts +++ b/libs/auth/src/angular/login/login-secondary-content.component.ts @@ -4,6 +4,8 @@ import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DefaultServerSettingsService } from "@bitwarden/common/platform/services/default-server-settings.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "@bitwarden/components"; @Component({ diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index d5180f56785..8674453cf10 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -29,6 +29,8 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts index a2b0b23d05c..5d3ecc1751d 100644 --- a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts +++ b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts @@ -9,6 +9,8 @@ import { LoginSuccessHandlerService } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/password-callout/password-callout.component.ts b/libs/auth/src/angular/password-callout/password-callout.component.ts index 6968f384f07..03fba52336f 100644 --- a/libs/auth/src/angular/password-callout/password-callout.component.ts +++ b/libs/auth/src/angular/password-callout/password-callout.component.ts @@ -6,6 +6,8 @@ import { Component, Input } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CalloutModule } from "@bitwarden/components"; @Component({ diff --git a/libs/auth/src/angular/password-callout/password-callout.stories.ts b/libs/auth/src/angular/password-callout/password-callout.stories.ts index ce5b698b2e8..58862049e10 100644 --- a/libs/auth/src/angular/password-callout/password-callout.stories.ts +++ b/libs/auth/src/angular/password-callout/password-callout.stories.ts @@ -2,6 +2,8 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { I18nMockService } from "@bitwarden/components"; import { PasswordCalloutComponent } from "./password-callout.component"; diff --git a/libs/auth/src/angular/password-hint/password-hint.component.ts b/libs/auth/src/angular/password-hint/password-hint.component.ts index cf24c68e10d..99eb06293e0 100644 --- a/libs/auth/src/angular/password-hint/password-hint.component.ts +++ b/libs/auth/src/angular/password-hint/password-hint.component.ts @@ -13,6 +13,8 @@ import { PasswordHintRequest } from "@bitwarden/common/auth/models/request/passw import { ClientType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts b/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts index 86f08e79748..8aac5f73ffe 100644 --- a/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts +++ b/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts @@ -15,6 +15,8 @@ import { } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogService, FormFieldModule, SelectModule, ToastService } from "@bitwarden/components"; import { SelfHostedEnvConfigDialogComponent } from "../../self-hosted-env-config-dialog/self-hosted-env-config-dialog.component"; diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index 981f7e554fb..353e3772c41 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -14,6 +14,8 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ToastService } from "@bitwarden/components"; import { diff --git a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts index ef032b72562..d4ecade52fe 100644 --- a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts +++ b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts @@ -6,6 +6,8 @@ import { ActivatedRoute, RouterModule } from "@angular/router"; import { Subject, firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ButtonModule, IconModule } from "@bitwarden/components"; import { RegistrationExpiredLinkIcon } from "../../icons/registration-expired-link.icon"; diff --git a/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts index 4b13c6666e2..bc873593a83 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts @@ -6,6 +6,8 @@ import { ActivatedRoute, RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "@bitwarden/components"; /** diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts index 44d1d720a8d..ea756c967c6 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts @@ -11,6 +11,8 @@ import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-a import { RegisterSendVerificationEmailRequest } from "@bitwarden/common/auth/models/request/registration/register-send-verification-email.request"; import { RegionConfig, Region } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts index 6047cc3d27a..e54e59a988a 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts @@ -15,6 +15,8 @@ import { Urls, } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts index 64478d63447..189c669423d 100644 --- a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts +++ b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts @@ -16,6 +16,8 @@ import { EnvironmentService, Region, } from "@bitwarden/common/platform/abstractions/environment.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogRef, AsyncActionsModule, diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts index 6001cb39085..95d54d589bc 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts @@ -1,6 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { FakeUserDecryptionOptions as UserDecryptionOptions, diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts index a4bc5f69b4c..ec274b9c4af 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserApiService, OrganizationUserResetPasswordEnrollmentRequest, diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index e0bdf8c26a1..f5a138af584 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -33,6 +33,8 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts index d8735d3fd54..cd4df5aee68 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts @@ -3,6 +3,8 @@ import { Component, Input, Output, EventEmitter } from "@angular/core"; import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts index 96fee95940b..22d3906bb48 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts @@ -6,6 +6,8 @@ import { ReactiveFormsModule, FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index 25235017bd1..2fff77b720e 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -12,6 +12,8 @@ import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.ser 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"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index 4efab4629c3..a8b435375db 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -13,6 +13,8 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi 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"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts index f334766bba9..abcbd557e0f 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts @@ -3,6 +3,8 @@ import { Component, Input } from "@angular/core"; import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index e52f08941d4..a2769e37c87 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -34,6 +34,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogService, ToastService } from "@bitwarden/components"; import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index b14a368e066..57637fe9118 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -38,6 +38,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts index ce6cef0d670..fb0d7101cad 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts @@ -7,6 +7,8 @@ import { TwoFactorService, } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogRef, ButtonModule, diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts index 58b1dca4cd1..8bb2f987d77 100644 --- a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts @@ -10,6 +10,8 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DIALOG_DATA, DialogRef, diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts b/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts index cb03f4e18f7..93ae6d7d77f 100644 --- a/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts +++ b/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts @@ -1,4 +1,6 @@ import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ButtonType } from "@bitwarden/components"; /** diff --git a/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts b/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts index ff4af51f732..7ea191ba2f9 100644 --- a/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts @@ -19,6 +19,8 @@ import { UserVerificationOptions } from "@bitwarden/common/auth/types/user-verif import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts index 82bc53bb147..c9f83902fe2 100644 --- a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts +++ b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts @@ -30,6 +30,8 @@ import { VaultTimeoutSettingsService, } from "@bitwarden/common/key-management/vault-timeout"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { FormFieldModule, SelectModule } from "@bitwarden/components"; type VaultTimeoutForm = FormGroup<{ diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index e4453359015..49543e2f2ce 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionAccessDetailsResponse, CollectionDetailsResponse, diff --git a/libs/common/src/admin-console/models/response/organization-export.response.ts b/libs/common/src/admin-console/models/response/organization-export.response.ts index 6e42fe14c0d..19a8dd9ad94 100644 --- a/libs/common/src/admin-console/models/response/organization-export.response.ts +++ b/libs/common/src/admin-console/models/response/organization-export.response.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionResponse } from "@bitwarden/admin-console/common"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts b/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts index 1f497dd8c5f..7e6a2b2a505 100644 --- a/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts +++ b/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserBulkPublicKeyResponse } from "@bitwarden/admin-console/common"; export class ProviderUserBulkPublicKeyResponse extends OrganizationUserBulkPublicKeyResponse {} diff --git a/libs/common/src/auth/models/request/registration/register-finish.request.ts b/libs/common/src/auth/models/request/registration/register-finish.request.ts index 645bcf05b1b..bb577053c5c 100644 --- a/libs/common/src/auth/models/request/registration/register-finish.request.ts +++ b/libs/common/src/auth/models/request/registration/register-finish.request.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { KeysRequest } from "../../../../models/request/keys.request"; diff --git a/libs/common/src/auth/models/request/set-password.request.ts b/libs/common/src/auth/models/request/set-password.request.ts index 5aa74068591..7206cd98623 100644 --- a/libs/common/src/auth/models/request/set-password.request.ts +++ b/libs/common/src/auth/models/request/set-password.request.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { KeysRequest } from "../../../models/request/keys.request"; diff --git a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts index 8c90fa379b4..8a107aa6c32 100644 --- a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts +++ b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserResetPasswordRequest } from "@bitwarden/admin-console/common"; export class UpdateTdeOffboardingPasswordRequest extends OrganizationUserResetPasswordRequest { diff --git a/libs/common/src/auth/models/request/update-temp-password.request.ts b/libs/common/src/auth/models/request/update-temp-password.request.ts index 8f922f9008b..39d6707e198 100644 --- a/libs/common/src/auth/models/request/update-temp-password.request.ts +++ b/libs/common/src/auth/models/request/update-temp-password.request.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserResetPasswordRequest } from "@bitwarden/admin-console/common"; export class UpdateTempPasswordRequest extends OrganizationUserResetPasswordRequest { diff --git a/libs/common/src/auth/models/response/identity-token.response.ts b/libs/common/src/auth/models/response/identity-token.response.ts index 5f128d340bf..3e2896eec64 100644 --- a/libs/common/src/auth/models/response/identity-token.response.ts +++ b/libs/common/src/auth/models/response/identity-token.response.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/auth/models/response/prelogin.response.ts b/libs/common/src/auth/models/response/prelogin.response.ts index b5ca78c3b79..e7c962242ce 100644 --- a/libs/common/src/auth/models/response/prelogin.response.ts +++ b/libs/common/src/auth/models/response/prelogin.response.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/auth/models/response/protected-device.response.ts b/libs/common/src/auth/models/response/protected-device.response.ts index 3cc3a3e0792..5695044c982 100644 --- a/libs/common/src/auth/models/response/protected-device.response.ts +++ b/libs/common/src/auth/models/response/protected-device.response.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { RotateableKeySet } from "@bitwarden/auth/common"; import { DeviceType } from "../../../enums"; diff --git a/libs/common/src/auth/services/auth.service.spec.ts b/libs/common/src/auth/services/auth.service.spec.ts index fc236c91a21..5dcb8c372e5 100644 --- a/libs/common/src/auth/services/auth.service.spec.ts +++ b/libs/common/src/auth/services/auth.service.spec.ts @@ -1,6 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index da70baf3999..9700efe02ca 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -11,6 +11,8 @@ import { switchMap, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts b/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts index 64d4fdf1c7b..7a3b8762c13 100644 --- a/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts +++ b/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts @@ -2,6 +2,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { PasswordRequest } from "../../models/request/password.request"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts index 76c2d443d1d..d5b787d69f0 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts @@ -1,7 +1,11 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index ef9c5ad3265..f491d7d5eb0 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -2,10 +2,14 @@ // @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserApiService, OrganizationUserResetPasswordEnrollmentRequest, } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; diff --git a/libs/common/src/auth/services/token.service.spec.ts b/libs/common/src/auth/services/token.service.spec.ts index a56853c479c..e67e522368f 100644 --- a/libs/common/src/auth/services/token.service.spec.ts +++ b/libs/common/src/auth/services/token.service.spec.ts @@ -3,6 +3,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { FakeSingleUserStateProvider, FakeGlobalStateProvider } from "../../../spec"; diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index 61c00f69215..2c6883272c3 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -3,6 +3,8 @@ import { Observable, combineLatest, firstValueFrom, map } from "rxjs"; import { Opaque } from "type-fest"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason, decodeJwtTokenToJson } from "@bitwarden/auth/common"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts index d56dd6dda3b..33f276a87f2 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts @@ -1,12 +1,16 @@ import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinLockType, PinServiceAbstraction, UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricsService, BiometricsStatus, diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index cfa6800deed..5837042b93f 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -2,7 +2,11 @@ // @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricsService, BiometricsStatus, diff --git a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts index 10444062349..56aa1139cda 100644 --- a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts +++ b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitwarden/auth/common"; import { LogService } from "../../../platform/abstractions/log.service"; diff --git a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts index cea4bf29737..2d42329d27a 100644 --- a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts +++ b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitwarden/auth/common"; import { LogService } from "../../../platform/abstractions/log.service"; diff --git a/libs/common/src/auth/types/verification.ts b/libs/common/src/auth/types/verification.ts index 307a584fb36..4f45a6fdeed 100644 --- a/libs/common/src/auth/types/verification.ts +++ b/libs/common/src/auth/types/verification.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfConfig } from "@bitwarden/key-management"; import { MasterKey } from "../../types/key"; diff --git a/libs/common/src/billing/services/organization-billing.service.spec.ts b/libs/common/src/billing/services/organization-billing.service.spec.ts index 7b194dff637..43457f810d1 100644 --- a/libs/common/src/billing/services/organization-billing.service.spec.ts +++ b/libs/common/src/billing/services/organization-billing.service.spec.ts @@ -12,6 +12,8 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SyncService } from "@bitwarden/common/platform/sync"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; describe("BillingAccountProfileStateService", () => { diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index fe5623fd5e6..b75134d28a8 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -5,6 +5,8 @@ import { Observable, of, switchMap } from "rxjs"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index 5e89e0a5cb7..84ebf981f03 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -2,7 +2,11 @@ // @ts-strict-ignore import { firstValueFrom, map, Observable, Subject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { RotateableKeySet, UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response"; diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts index de9de5d781a..c1b291c086a 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts @@ -3,11 +3,15 @@ import { matches, mock } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptionsServiceAbstraction, UserDecryptionOptions, } from "@bitwarden/auth/common"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service"; diff --git a/libs/common/src/key-management/key-connector/models/set-key-connector-key.request.ts b/libs/common/src/key-management/key-connector/models/set-key-connector-key.request.ts index 14132ab79f2..fec38b1d72d 100644 --- a/libs/common/src/key-management/key-connector/models/set-key-connector-key.request.ts +++ b/libs/common/src/key-management/key-connector/models/set-key-connector-key.request.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfConfig, KdfType } from "@bitwarden/key-management"; import { KeysRequest } from "../../../models/request/keys.request"; diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts index d5443cfc52c..6049d4db5a1 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts @@ -3,6 +3,8 @@ import { firstValueFrom, of, timeout, TimeoutError } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.ts index fc0b64f707b..905bc42defe 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.ts @@ -2,8 +2,12 @@ // @ts-strict-ignore import { combineLatest, filter, firstValueFrom, Observable, of, switchMap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { Argon2KdfConfig, KdfConfig, diff --git a/libs/common/src/key-management/services/default-process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts index 860dac54855..e43fa5f0977 100644 --- a/libs/common/src/key-management/services/default-process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -2,7 +2,11 @@ // @ts-strict-ignore import { firstValueFrom, map, timeout } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricStateService } from "@bitwarden/key-management"; import { AccountService } from "../../auth/abstractions/account.service"; diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts index b5e9544b01b..349aa474872 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts @@ -3,11 +3,15 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, map, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction, FakeUserDecryptionOptions as UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricStateService, KeyService } from "@bitwarden/key-management"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts index 07b8a8c297d..16e38ae0b52 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts @@ -14,10 +14,14 @@ import { tap, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricStateService, KeyService } from "@bitwarden/key-management"; import { PolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts index 5fdae07b2d7..b17e85ca9c4 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts @@ -3,8 +3,14 @@ import { MockProxy, any, mock } from "jest-mock-extended"; import { BehaviorSubject, from, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricsService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec"; diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts index d71b8972727..131f826fd33 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts @@ -2,8 +2,14 @@ // @ts-strict-ignore import { combineLatest, concatMap, filter, firstValueFrom, map, timeout } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricsService } from "@bitwarden/key-management"; import { SearchService } from "../../../abstractions/search.service"; diff --git a/libs/common/src/models/export/collection-with-id.export.ts b/libs/common/src/models/export/collection-with-id.export.ts index ef850bc6039..a93f07b54e5 100644 --- a/libs/common/src/models/export/collection-with-id.export.ts +++ b/libs/common/src/models/export/collection-with-id.export.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common"; import { CollectionExport } from "./collection.export"; diff --git a/libs/common/src/models/export/collection.export.ts b/libs/common/src/models/export/collection.export.ts index 89137c34875..3f913a8c3e3 100644 --- a/libs/common/src/models/export/collection.export.ts +++ b/libs/common/src/models/export/collection.export.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common"; import { EncString } from "../../platform/models/domain/enc-string"; diff --git a/libs/common/src/models/request/import-organization-ciphers.request.ts b/libs/common/src/models/request/import-organization-ciphers.request.ts index 759b69b21e3..69f23f64b64 100644 --- a/libs/common/src/models/request/import-organization-ciphers.request.ts +++ b/libs/common/src/models/request/import-organization-ciphers.request.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionWithIdRequest } from "@bitwarden/admin-console/common"; import { CipherRequest } from "../../vault/models/request/cipher.request"; diff --git a/libs/common/src/models/request/kdf.request.ts b/libs/common/src/models/request/kdf.request.ts index f0bd376317f..7ffdbcb4a4b 100644 --- a/libs/common/src/models/request/kdf.request.ts +++ b/libs/common/src/models/request/kdf.request.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { PasswordRequest } from "../../auth/models/request/password.request"; diff --git a/libs/common/src/platform/abstractions/key-generation.service.ts b/libs/common/src/platform/abstractions/key-generation.service.ts index 8314efe3469..91c630ed638 100644 --- a/libs/common/src/platform/abstractions/key-generation.service.ts +++ b/libs/common/src/platform/abstractions/key-generation.service.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfConfig } from "@bitwarden/key-management"; import { CsprngArray } from "../../types/csprng"; diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index f9c5ab4a843..a1e914da531 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -8,6 +8,8 @@ import { Observable, of, switchMap } from "rxjs"; import { getHostname, parse } from "tldts"; import { Merge } from "type-fest"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/platform/models/domain/enc-string.spec.ts b/libs/common/src/platform/models/domain/enc-string.spec.ts index 1ab61745eb3..f565174ba9c 100644 --- a/libs/common/src/platform/models/domain/enc-string.spec.ts +++ b/libs/common/src/platform/models/domain/enc-string.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { makeEncString, makeStaticByteArray } from "../../../../spec"; diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts index bf834e8dd93..9dca079bdba 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, bufferCount, firstValueFrom, ObservedValueOf, Subject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { awaitAsync } from "../../../../spec"; diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.ts index 40c93f8f22a..4cbc8227364 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.ts @@ -11,6 +11,8 @@ import { switchMap, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { AccountService } from "../../../auth/abstractions/account.service"; diff --git a/libs/common/src/platform/services/container.service.ts b/libs/common/src/platform/services/container.service.ts index 1428b2bbd7c..501c8ace92c 100644 --- a/libs/common/src/platform/services/container.service.ts +++ b/libs/common/src/platform/services/container.service.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/platform/services/key-generation.service.spec.ts b/libs/common/src/platform/services/key-generation.service.spec.ts index 0a9e997b428..4fdad48e0fa 100644 --- a/libs/common/src/platform/services/key-generation.service.spec.ts +++ b/libs/common/src/platform/services/key-generation.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PBKDF2KdfConfig, Argon2KdfConfig } from "@bitwarden/key-management"; import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service"; diff --git a/libs/common/src/platform/services/key-generation.service.ts b/libs/common/src/platform/services/key-generation.service.ts index dcd1f4f95d7..49f99eb79a9 100644 --- a/libs/common/src/platform/services/key-generation.service.ts +++ b/libs/common/src/platform/services/key-generation.service.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfConfig, PBKDF2KdfConfig, Argon2KdfConfig, KdfType } from "@bitwarden/key-management"; import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service"; diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts index a66b2a9cb6f..6531be58f05 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfConfigService, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; import { BitwardenClient } from "@bitwarden/sdk-internal"; diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 5c381c7dd1b..7027b5134a0 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -14,6 +14,8 @@ import { throwIfEmpty, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key-management"; import { BitwardenClient, diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts index 84511d1e71a..b9f189c5f06 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DefaultKeyService } from "@bitwarden/key-management"; import { CsprngArray } from "../../types/csprng"; diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.ts index bf64c13b060..33537cd0e2d 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/platform/sync/core-sync.service.ts b/libs/common/src/platform/sync/core-sync.service.ts index 4020c75f764..63f9ab17fb3 100644 --- a/libs/common/src/platform/sync/core-sync.service.ts +++ b/libs/common/src/platform/sync/core-sync.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { firstValueFrom, map, Observable, of, switchMap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/platform/sync/default-sync.service.spec.ts b/libs/common/src/platform/sync/default-sync.service.spec.ts index ded06c8be6b..29543672dbe 100644 --- a/libs/common/src/platform/sync/default-sync.service.spec.ts +++ b/libs/common/src/platform/sync/default-sync.service.spec.ts @@ -1,12 +1,18 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason, UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { Matrix } from "../../../spec/matrix"; diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index e9f6c60af64..6e1a8f28443 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -2,11 +2,15 @@ // @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionData, CollectionDetailsResponse, CollectionService, } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; // FIXME: remove `src` and fix import diff --git a/libs/common/src/platform/sync/sync.response.ts b/libs/common/src/platform/sync/sync.response.ts index bc94eff67a4..5055652a0d1 100644 --- a/libs/common/src/platform/sync/sync.response.ts +++ b/libs/common/src/platform/sync/sync.response.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionDetailsResponse } from "@bitwarden/admin-console/common"; import { PolicyResponse } from "../../admin-console/models/response/policy.response"; diff --git a/libs/common/src/services/api.service.spec.ts b/libs/common/src/services/api.service.spec.ts index eca6066b9b7..fffe0478254 100644 --- a/libs/common/src/services/api.service.spec.ts +++ b/libs/common/src/services/api.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { TokenService } from "../auth/abstractions/token.service"; diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 639daa7c658..95aea41e68b 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -2,12 +2,16 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionAccessDetailsResponse, CollectionDetailsResponse, CollectionRequest, CollectionResponse, } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "../abstractions/api.service"; diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts index 66edc5a4838..255fd9b4aff 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, Subject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts index c91181a004a..c2e9b305618 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts @@ -12,6 +12,8 @@ import { takeWhile, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index 7112ad7f751..8df9a144108 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { makeStaticByteArray, mockEnc } from "../../../../../spec"; diff --git a/libs/common/src/tools/send/services/send.service.abstraction.ts b/libs/common/src/tools/send/services/send.service.abstraction.ts index 921f0565624..0cf951e4197 100644 --- a/libs/common/src/tools/send/services/send.service.abstraction.ts +++ b/libs/common/src/tools/send/services/send.service.abstraction.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 611cc9c7b76..777bc54f299 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { diff --git a/libs/common/src/tools/send/services/send.service.ts b/libs/common/src/tools/send/services/send.service.ts index 3a5bcbe997b..2556fa2e908 100644 --- a/libs/common/src/tools/send/services/send.service.ts +++ b/libs/common/src/tools/send/services/send.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable, concatMap, distinctUntilChanged, firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PBKDF2KdfConfig, KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index a67dfcef8b9..fc809058161 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; import { UriMatchStrategySetting } from "../../models/domain/domain-service"; diff --git a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts index b7241e3ae37..7324fe22c8d 100644 --- a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index eab67320679..f03ce927082 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec"; diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index a889f0b969c..5c98ceda9f7 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { CipherType as SdkCipherType, diff --git a/libs/common/src/vault/services/cipher-authorization.service.spec.ts b/libs/common/src/vault/services/cipher-authorization.service.spec.ts index 33af28842ca..01574d04df5 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.spec.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { Observable, firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; diff --git a/libs/common/src/vault/services/cipher-authorization.service.ts b/libs/common/src/vault/services/cipher-authorization.service.ts index b415760a035..f30e80cdfb8 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.ts @@ -1,5 +1,7 @@ import { combineLatest, map, Observable, of, shareReplay, switchMap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index b15bc4a9112..9e56bac2ca0 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, map, of } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CipherDecryptionKeys, KeyService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 6bea56baa5e..2693d9d4644 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -4,6 +4,8 @@ import { combineLatest, filter, firstValueFrom, map, Observable, Subject, switch import { SemVer } from "semver"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 0c61efc288a..38dea851456 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { makeEncString } from "../../../../spec"; diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index ba87f1c3148..12d02958049 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable, Subject, firstValueFrom, map, shareReplay, switchMap, merge } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 2fee88852b5..7b8f49b796b 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -18,6 +18,8 @@ import * as JSZip from "jszip"; import { Observable, Subject, lastValueFrom, combineLatest, firstValueFrom } from "rxjs"; import { combineLatestWith, filter, map, switchMap, takeUntil } from "rxjs/operators"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { safeProvider, SafeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; diff --git a/libs/importer/src/importers/base-importer.ts b/libs/importer/src/importers/base-importer.ts index 0594b6014e8..9033997a475 100644 --- a/libs/importer/src/importers/base-importer.ts +++ b/libs/importer/src/importers/base-importer.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import * as papa from "papaparse"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index af29d8263c6..4291f7b1ab2 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/libs/importer/src/importers/padlock-csv-importer.ts b/libs/importer/src/importers/padlock-csv-importer.ts index f4943e5979b..86b569fbc52 100644 --- a/libs/importer/src/importers/padlock-csv-importer.ts +++ b/libs/importer/src/importers/padlock-csv-importer.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { ImportResult } from "../models/import-result"; diff --git a/libs/importer/src/importers/passpack-csv-importer.ts b/libs/importer/src/importers/passpack-csv-importer.ts index a426d6db416..17b0c148896 100644 --- a/libs/importer/src/importers/passpack-csv-importer.ts +++ b/libs/importer/src/importers/passpack-csv-importer.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { ImportResult } from "../models/import-result"; diff --git a/libs/importer/src/models/import-result.ts b/libs/importer/src/models/import-result.ts index 9fddbb420a1..9d94b410e7b 100644 --- a/libs/importer/src/models/import-result.ts +++ b/libs/importer/src/models/import-result.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; diff --git a/libs/importer/src/services/import-collection.service.abstraction.ts b/libs/importer/src/services/import-collection.service.abstraction.ts index 539232ef094..48e6e7a4a8c 100644 --- a/libs/importer/src/services/import-collection.service.abstraction.ts +++ b/libs/importer/src/services/import-collection.service.abstraction.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; export abstract class ImportCollectionServiceAbstraction { diff --git a/libs/importer/src/services/import.service.abstraction.ts b/libs/importer/src/services/import.service.abstraction.ts index 5f7a31bcc14..d869dc71cc7 100644 --- a/libs/importer/src/services/import.service.abstraction.ts +++ b/libs/importer/src/services/import.service.abstraction.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index 908f062ecc1..30309a3d9c2 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index bd18e78d542..adfa427c660 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionWithIdRequest, diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index f1f1286dfc5..6d2e8fd20ec 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, bufferCount, firstValueFrom, lastValueFrom, of, take } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 9372dafd3ea..1d4fcc86a0c 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -10,6 +10,8 @@ import { switchMap, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; import { BaseEncryptedOrganizationKey } from "@bitwarden/common/admin-console/models/domain/encrypted-organization-key"; diff --git a/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts b/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts index 639cf562caa..71f12340ebc 100644 --- a/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts +++ b/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index c5256c841d9..3d68b7124c1 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -12,6 +12,8 @@ import { } from "@storybook/angular"; import { BehaviorSubject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { NudgeStatus, NudgesService } from "@bitwarden/angular/vault"; diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts index 074b4b9e4b4..1e9916e76a4 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts @@ -5,6 +5,8 @@ import { By } from "@angular/platform-browser"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts index 82615368b91..a1cf33c449b 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts @@ -6,6 +6,8 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms"; import { concatMap, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts index 8e1dde22324..a91a84e91c1 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts @@ -3,6 +3,8 @@ import { inject, Injectable } from "@angular/core"; import { combineLatest, filter, firstValueFrom, map, switchMap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index cf78e78a65f..02968d6d149 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -2,6 +2,8 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnChanges, OnDestroy } from "@angular/core"; import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.spec.ts b/libs/vault/src/cipher-view/item-details/item-details-v2.component.spec.ts index da3d790368c..f093cd020b5 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.spec.ts +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.spec.ts @@ -1,6 +1,8 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts index 1335df74bb9..fbedcbb54df 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts @@ -3,6 +3,8 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index db62f096faa..faa2dae072a 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -24,6 +24,8 @@ import { tap, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { From 0e0be0a3decf2ae93976ebf6257086a09de718c2 Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Fri, 23 May 2025 09:07:32 -0400 Subject: [PATCH 12/41] ignore one eslint error (#14896) --- libs/vault/src/components/assign-collections.component.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/vault/src/components/assign-collections.component.spec.ts b/libs/vault/src/components/assign-collections.component.spec.ts index d6707cd1064..800bf5393ad 100644 --- a/libs/vault/src/components/assign-collections.component.spec.ts +++ b/libs/vault/src/components/assign-collections.component.spec.ts @@ -3,6 +3,8 @@ import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; From c6af80f3eb812006e56c57be7f1c3c290a06ce1b Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Fri, 23 May 2025 11:48:23 -0400 Subject: [PATCH 13/41] PM-21651 [For Automation Purposes] add test IDs to notification bar (#14863) * PM-21651 [For Automation Purposes] Please add IDs to some of the main components * option items * dynamic test id * mitigate feedback * clean up logic --- .../buttons/option-selection-button.ts | 3 ++ .../option-selection-button.lit-stories.ts | 2 + .../confirmation/container.lit-stories.ts | 8 +++- .../notification/container.lit-stories.ts | 5 ++- .../option-selection.lit-stories.ts | 5 ++- .../notification/confirmation/container.ts | 4 +- .../components/notification/container.ts | 4 +- .../option-selection/option-item.ts | 3 ++ .../option-selection/option-items.ts | 3 ++ .../option-selection/option-selection.ts | 5 +++ .../content/components/rows/button-row.ts | 1 + apps/browser/src/autofill/notification/bar.ts | 42 +++++++++++++++---- 12 files changed, 70 insertions(+), 15 deletions(-) diff --git a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts index 3912c791d34..ddefd02f6e1 100644 --- a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts @@ -10,6 +10,7 @@ import { AngleUp, AngleDown } from "../icons"; export type OptionSelectionButtonProps = { disabled: boolean; icon?: Option["icon"]; + id: string; text?: string; theme: Theme; toggledOn: boolean; @@ -19,6 +20,7 @@ export type OptionSelectionButtonProps = { export function OptionSelectionButton({ disabled, icon, + id, text, theme, toggledOn, @@ -31,6 +33,7 @@ export function OptionSelectionButton({ return html` diff --git a/apps/browser/src/auth/popup/account-switching/account.component.html b/apps/browser/src/auth/popup/account-switching/account.component.html index d2e15d31899..d22ce9c9366 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.html +++ b/apps/browser/src/auth/popup/account-switching/account.component.html @@ -32,13 +32,13 @@ - + From 04ed114e0ef2845527535778284142daa7ef35ed Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 26 May 2025 00:30:52 +0200 Subject: [PATCH 18/41] [BEEEP/PM-8492] Add autostart for flatpak (#12016) * Add autostart for flatpak via ashpd * Fix clippy errors * Cargo fmt * Fix clippy --- apps/desktop/desktop_native/Cargo.lock | 1 + apps/desktop/desktop_native/Cargo.toml | 1 + apps/desktop/desktop_native/core/Cargo.toml | 1 + .../core/src/autostart/linux.rs | 21 ++++++++++ .../desktop_native/core/src/autostart/mod.rs | 5 +++ .../core/src/autostart/unimplemented.rs | 5 +++ apps/desktop/desktop_native/core/src/lib.rs | 1 + apps/desktop/desktop_native/napi/index.d.ts | 3 ++ apps/desktop/desktop_native/napi/src/lib.rs | 10 +++++ apps/desktop/src/main/messaging.main.ts | 39 ++++++++++++------- 10 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 apps/desktop/desktop_native/core/src/autostart/linux.rs create mode 100644 apps/desktop/desktop_native/core/src/autostart/mod.rs create mode 100644 apps/desktop/desktop_native/core/src/autostart/unimplemented.rs diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index e5d90446ddc..34819a3981b 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -863,6 +863,7 @@ dependencies = [ "anyhow", "arboard", "argon2", + "ashpd", "base64", "bitwarden-russh", "byteorder", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 71da53c867d..c4a2ed98e70 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -13,6 +13,7 @@ aes = "=0.8.4" anyhow = "=1.0.94" arboard = { version = "=3.5.0", default-features = false } argon2 = "=0.5.3" +ashpd = "=0.11.0" base64 = "=0.22.1" bindgen = "=0.71.1" bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "3d48f140fd506412d186203238993163a8c4e536" } diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index b71081aaa1f..7cd67dbad6a 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -85,6 +85,7 @@ desktop_objc = { path = "../objc" } [target.'cfg(target_os = "linux")'.dependencies] oo7 = { workspace = true } libc = { workspace = true } +ashpd = { workspace = true } zbus = { workspace = true, optional = true } zbus_polkit = { workspace = true, optional = true } diff --git a/apps/desktop/desktop_native/core/src/autostart/linux.rs b/apps/desktop/desktop_native/core/src/autostart/linux.rs new file mode 100644 index 00000000000..1fd02a6ea5d --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autostart/linux.rs @@ -0,0 +1,21 @@ +use anyhow::Result; +use ashpd::desktop::background::Background; + +pub async fn set_autostart(autostart: bool, params: Vec) -> Result<()> { + let request = if params.is_empty() { + Background::request().auto_start(autostart) + } else { + Background::request().command(params).auto_start(autostart) + }; + + match request.send().await.and_then(|r| r.response()) { + Ok(response) => { + println!("[ASHPD] Autostart enabled: {:?}", response); + Ok(()) + } + Err(err) => { + println!("[ASHPD] Error enabling autostart: {}", err); + Err(anyhow::anyhow!("error enabling autostart {}", err)) + } + } +} diff --git a/apps/desktop/desktop_native/core/src/autostart/mod.rs b/apps/desktop/desktop_native/core/src/autostart/mod.rs new file mode 100644 index 00000000000..78e27eb433e --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autostart/mod.rs @@ -0,0 +1,5 @@ +#[cfg_attr(target_os = "linux", path = "linux.rs")] +#[cfg_attr(target_os = "windows", path = "unimplemented.rs")] +#[cfg_attr(target_os = "macos", path = "unimplemented.rs")] +mod autostart_impl; +pub use autostart_impl::*; diff --git a/apps/desktop/desktop_native/core/src/autostart/unimplemented.rs b/apps/desktop/desktop_native/core/src/autostart/unimplemented.rs new file mode 100644 index 00000000000..14f567bdc65 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autostart/unimplemented.rs @@ -0,0 +1,5 @@ +use anyhow::Result; + +pub async fn set_autostart(_autostart: bool, _params: Vec) -> Result<()> { + unimplemented!(); +} diff --git a/apps/desktop/desktop_native/core/src/lib.rs b/apps/desktop/desktop_native/core/src/lib.rs index 0a16ee65be3..a72ec04e9c2 100644 --- a/apps/desktop/desktop_native/core/src/lib.rs +++ b/apps/desktop/desktop_native/core/src/lib.rs @@ -1,4 +1,5 @@ pub mod autofill; +pub mod autostart; pub mod biometric; pub mod clipboard; pub mod crypto; diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 952f2571c5d..b3c6f715e98 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -111,6 +111,9 @@ export declare namespace ipc { send(message: string): number } } +export declare namespace autostart { + export function setAutostart(autostart: boolean, params: Array): Promise +} export declare namespace autofill { export function runCommand(value: string): Promise export const enum UserVerification { diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 37796ef6f59..079872a3b03 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -477,6 +477,16 @@ pub mod ipc { } } +#[napi] +pub mod autostart { + #[napi] + pub async fn set_autostart(autostart: bool, params: Vec) -> napi::Result<()> { + desktop_core::autostart::set_autostart(autostart, params) + .await + .map_err(|e| napi::Error::from_reason(format!("Error setting autostart - {e} - {e:?}"))) + } +} + #[napi] pub mod autofill { use desktop_core::ipc::server::{Message, MessageType}; diff --git a/apps/desktop/src/main/messaging.main.ts b/apps/desktop/src/main/messaging.main.ts index bb4063d64fd..556fa293108 100644 --- a/apps/desktop/src/main/messaging.main.ts +++ b/apps/desktop/src/main/messaging.main.ts @@ -6,8 +6,11 @@ import * as path from "path"; import { app, ipcMain } from "electron"; import { firstValueFrom } from "rxjs"; +import { autostart } from "@bitwarden/desktop-napi"; + import { Main } from "../main"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; +import { isFlatpak } from "../utils"; import { MenuUpdateRequest } from "./menu/menu.updater"; @@ -122,20 +125,24 @@ export class MessagingMain { private addOpenAtLogin() { if (process.platform === "linux") { - const data = `[Desktop Entry] -Type=Application -Version=${app.getVersion()} -Name=Bitwarden -Comment=Bitwarden startup script -Exec=${app.getPath("exe")} -StartupNotify=false -Terminal=false`; + if (isFlatpak()) { + autostart.setAutostart(true, []).catch((e) => {}); + } else { + const data = `[Desktop Entry] + Type=Application + Version=${app.getVersion()} + Name=Bitwarden + Comment=Bitwarden startup script + Exec=${app.getPath("exe")} + StartupNotify=false + Terminal=false`; - const dir = path.dirname(this.linuxStartupFile()); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); + const dir = path.dirname(this.linuxStartupFile()); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + fs.writeFileSync(this.linuxStartupFile(), data); } - fs.writeFileSync(this.linuxStartupFile(), data); } else { app.setLoginItemSettings({ openAtLogin: true }); } @@ -143,8 +150,12 @@ Terminal=false`; private removeOpenAtLogin() { if (process.platform === "linux") { - if (fs.existsSync(this.linuxStartupFile())) { - fs.unlinkSync(this.linuxStartupFile()); + if (isFlatpak()) { + autostart.setAutostart(false, []).catch((e) => {}); + } else { + if (fs.existsSync(this.linuxStartupFile())) { + fs.unlinkSync(this.linuxStartupFile()); + } } } else { app.setLoginItemSettings({ openAtLogin: false }); From 56b565a47ebaf6fc49a021a86127f42d554aa7d3 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Mon, 26 May 2025 03:21:30 -0400 Subject: [PATCH 19/41] Add lowdb to ignored dependencies (#14907) * Added lowdb to ignored dependencies * Changed to allowedVersions instead of ignoring for more context. --- .github/renovate.json5 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 0ebc2c210a2..1b84ccdab01 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -405,6 +405,12 @@ commitMessagePrefix: "[deps] KM:", reviewers: ["team:team-key-management-dev"], }, + { + // Any versions of lowdb above 1.0.0 are not compatible with CommonJS. + matchPackageNames: ["lowdb"], + allowedVersions: "1.0.0", + description: "Higher versions of lowdb are not compatible with CommonJS", + }, ], ignoreDeps: ["@types/koa-bodyparser", "bootstrap", "node-ipc", "@bitwarden/sdk-internal"], } From 745ab219467fbe31ce0e05308ff3232c0067e1e1 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 26 May 2025 14:38:02 +0200 Subject: [PATCH 20/41] Remove unused imports in browser and desktop (#14875) Removes unused imports from browser and desktop. These were missed in #14795. --- .../autofill/popup/fido2/fido2-cipher-row.component.ts | 4 ---- .../autofill/popup/settings/notifications.component.ts | 2 -- .../src/platform/popup/layout/popup-layout.stories.ts | 3 --- .../popup/generator/credential-generator.component.ts | 2 -- .../send-v2/send-created/send-created.component.ts | 3 +-- .../open-attachments/open-attachments.component.ts | 10 ++-------- .../autofill-vault-list-items.component.ts | 9 +-------- .../vault-list-items-container.component.ts | 1 - .../popup/settings/download-bitwarden.component.ts | 2 -- .../trash-list-items-container.component.ts | 1 - .../popup/settings/vault-settings-v2.component.ts | 2 -- .../src/platform/components/approve-ssh-request.ts | 2 -- 12 files changed, 4 insertions(+), 37 deletions(-) diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts index 98e73d2174c..02df3ffe9b3 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts @@ -10,8 +10,6 @@ import { ButtonModule, IconButtonModule, ItemModule, - SectionComponent, - SectionHeaderComponent, TypographyModule, } from "@bitwarden/components"; @@ -27,8 +25,6 @@ import { IconButtonModule, ItemModule, JslibModule, - SectionComponent, - SectionHeaderComponent, TypographyModule, ], }) diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.ts b/apps/browser/src/autofill/popup/settings/notifications.component.ts index be447e3f885..476b601c4e5 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.ts +++ b/apps/browser/src/autofill/popup/settings/notifications.component.ts @@ -18,7 +18,6 @@ import { } from "@bitwarden/components"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -31,7 +30,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co RouterModule, PopupPageComponent, PopupHeaderComponent, - PopupFooterComponent, PopOutComponent, ItemModule, CardComponent, diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index f20049f6cde..48940f5fa10 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -185,7 +185,6 @@ class MockVaultPageComponent {} PopupPageComponent, PopupHeaderComponent, MockAddButtonComponent, - MockPopoutButtonComponent, MockCurrentAccountComponent, VaultComponent, ], @@ -290,9 +289,7 @@ class MockSettingsPageComponent {} PopupHeaderComponent, PopupFooterComponent, ButtonModule, - MockAddButtonComponent, MockPopoutButtonComponent, - MockCurrentAccountComponent, VaultComponent, IconButtonModule, ], diff --git a/apps/browser/src/tools/popup/generator/credential-generator.component.ts b/apps/browser/src/tools/popup/generator/credential-generator.component.ts index 9c1af07efdd..a2ef4be6620 100644 --- a/apps/browser/src/tools/popup/generator/credential-generator.component.ts +++ b/apps/browser/src/tools/popup/generator/credential-generator.component.ts @@ -7,7 +7,6 @@ import { GeneratorModule } from "@bitwarden/generator-components"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -22,7 +21,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co PopOutComponent, PopupHeaderComponent, PopupPageComponent, - PopupFooterComponent, RouterModule, ItemModule, ], diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts index 7191040ac6f..7680e05dd5b 100644 --- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts @@ -3,7 +3,7 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute, Router, RouterLink, RouterModule } from "@angular/router"; +import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -31,7 +31,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page PopOutComponent, PopupHeaderComponent, PopupPageComponent, - RouterLink, RouterModule, PopupFooterComponent, IconModule, diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index 9189ea51313..d44b54bcb96 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -18,13 +18,7 @@ import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { - BadgeModule, - CardComponent, - ItemModule, - ToastService, - TypographyModule, -} from "@bitwarden/components"; +import { BadgeModule, ItemModule, ToastService, TypographyModule } from "@bitwarden/components"; import BrowserPopupUtils from "../../../../../../platform/popup/browser-popup-utils"; import { FilePopoutUtilsService } from "../../../../../../tools/popup/services/file-popout-utils.service"; @@ -33,7 +27,7 @@ import { FilePopoutUtilsService } from "../../../../../../tools/popup/services/f standalone: true, selector: "app-open-attachments", templateUrl: "./open-attachments.component.html", - imports: [BadgeModule, CommonModule, ItemModule, JslibModule, TypographyModule, CardComponent], + imports: [BadgeModule, CommonModule, ItemModule, JslibModule, TypographyModule], }) export class OpenAttachmentsComponent implements OnInit { /** Cipher `id` */ diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts index 72d51776f7b..bdc0d7ae5bc 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts @@ -6,12 +6,7 @@ import { combineLatest, map, Observable } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; -import { - IconButtonModule, - SectionComponent, - SectionHeaderComponent, - TypographyModule, -} from "@bitwarden/components"; +import { IconButtonModule, TypographyModule } from "@bitwarden/components"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; @@ -23,11 +18,9 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/ standalone: true, imports: [ CommonModule, - SectionComponent, TypographyModule, VaultListItemsContainerComponent, JslibModule, - SectionHeaderComponent, IconButtonModule, ], selector: "app-autofill-vault-list-items", diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index 6df1bdf8ae5..073d49333b5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -74,7 +74,6 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options ScrollingModule, DisclosureComponent, DisclosureTriggerForDirective, - DecryptionFailureDialogComponent, ], selector: "app-vault-list-items-container", templateUrl: "vault-list-items-container.component.html", diff --git a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts index fa7efa87bda..0287b7d504f 100644 --- a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts +++ b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts @@ -9,7 +9,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CardComponent, LinkModule, TypographyModule } from "@bitwarden/components"; -import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -26,7 +25,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co PopOutComponent, CardComponent, TypographyModule, - CurrentAccountComponent, LinkModule, ], }) diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index cbfc89bf922..b4899e12eda 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -49,7 +49,6 @@ import { PopupCipherView } from "../../views/popup-cipher.view"; IconButtonModule, OrgIconDirective, TypographyModule, - DecryptionFailureDialogComponent, ], changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts index c969f0436df..049e853d2cd 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts @@ -10,7 +10,6 @@ import { ItemModule, ToastOptions, ToastService } from "@bitwarden/components"; import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -22,7 +21,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co JslibModule, RouterModule, PopupPageComponent, - PopupFooterComponent, PopupHeaderComponent, PopOutComponent, ItemModule, diff --git a/apps/desktop/src/platform/components/approve-ssh-request.ts b/apps/desktop/src/platform/components/approve-ssh-request.ts index c6c7388ecf1..515fd94ecd6 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.ts +++ b/apps/desktop/src/platform/components/approve-ssh-request.ts @@ -13,7 +13,6 @@ import { IconButtonModule, DialogService, } from "@bitwarden/components"; -import { CipherFormGeneratorComponent } from "@bitwarden/vault"; export interface ApproveSshRequestParams { cipherName: string; @@ -30,7 +29,6 @@ export interface ApproveSshRequestParams { DialogModule, CommonModule, JslibModule, - CipherFormGeneratorComponent, ButtonModule, IconButtonModule, ReactiveFormsModule, From 43c963032db7f8a92fc97e24f7044af22f5751cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 16:25:30 +0200 Subject: [PATCH 21/41] [deps] Architecture: Update lint-staged to v16 (#14925) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 160 ++++++---------------------------------------- package.json | 2 +- 2 files changed, 22 insertions(+), 140 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e2623f89cc..88302755623 100644 --- a/package-lock.json +++ b/package-lock.json @@ -153,7 +153,7 @@ "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", "json5": "2.2.3", - "lint-staged": "15.5.1", + "lint-staged": "16.0.0", "mini-css-extract-plugin": "2.9.2", "nx": "20.8.0", "postcss": "8.5.3", @@ -25970,28 +25970,28 @@ } }, "node_modules/lint-staged": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.1.tgz", - "integrity": "sha512-6m7u8mue4Xn6wK6gZvSCQwBvMBR36xfY24nF5bMTf2MHDYG6S3yhJuOgdYVw99hsjyDt2d4z168b3naI8+NWtQ==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.0.0.tgz", + "integrity": "sha512-sUCprePs6/rbx4vKC60Hez6X10HPkpDJaGcy3D1NdwR7g1RcNkWL8q9mJMreOqmHBTs+1sNFp+wOiX9fr+hoOQ==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^5.4.1", "commander": "^13.1.0", "debug": "^4.4.0", - "execa": "^8.0.1", "lilconfig": "^3.1.3", - "listr2": "^8.2.5", + "listr2": "^8.3.3", "micromatch": "^4.0.8", + "nano-spawn": "^1.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", - "yaml": "^2.7.0" + "yaml": "^2.7.1" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": ">=18.12.0" + "node": ">=20.18" }, "funding": { "url": "https://opencollective.com/lint-staged" @@ -26077,53 +26077,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lint-staged/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", @@ -26137,19 +26090,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lint-staged/node_modules/listr2": { "version": "8.3.3", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", @@ -26168,64 +26108,6 @@ "node": ">=18.0.0" } }, - "node_modules/lint-staged/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lint-staged/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -26277,19 +26159,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/lint-staged/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lint-staged/node_modules/wrap-ansi": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", @@ -28632,6 +28501,19 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nano-spawn": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz", + "integrity": "sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", diff --git a/package.json b/package.json index ce6adf3009d..8a8e80bd632 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", "json5": "2.2.3", - "lint-staged": "15.5.1", + "lint-staged": "16.0.0", "mini-css-extract-plugin": "2.9.2", "nx": "20.8.0", "postcss": "8.5.3", From beb00a206b7b48f4f053b394b06160b5dd2c3f2c Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 26 May 2025 17:02:28 +0200 Subject: [PATCH 22/41] Add UUID helpers to the SDK (#14939) * Add UUID helpers to the SDK * Address review feedback --- .../platform/abstractions/sdk/sdk.service.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index 3adf3291bbf..07dfb2aa0df 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -1,9 +1,10 @@ import { Observable } from "rxjs"; -import { BitwardenClient } from "@bitwarden/sdk-internal"; +import { BitwardenClient, Uuid } from "@bitwarden/sdk-internal"; import { UserId } from "../../../types/guid"; import { Rc } from "../../misc/reference-counting/rc"; +import { Utils } from "../../misc/utils"; export class UserNotLoggedInError extends Error { constructor(userId: UserId) { @@ -11,6 +12,30 @@ export class UserNotLoggedInError extends Error { } } +export class InvalidUuid extends Error { + constructor(uuid: string) { + super(`Invalid UUID: ${uuid}`); + } +} + +/** + * Converts a string to UUID. Will throw an error if the UUID is non valid. + */ +export function asUuid(uuid: string): T { + if (Utils.isGuid(uuid)) { + return uuid as T; + } + + throw new InvalidUuid(uuid); +} + +/** + * Converts a UUID to the string representation. + */ +export function uuidToString(uuid: T): string { + return uuid as unknown as string; +} + export abstract class SdkService { /** * Retrieve the version of the SDK. From a6e2012087f1dc8e49c4b2d70a2f04a6a8ddd37c Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 27 May 2025 10:03:54 +0200 Subject: [PATCH 23/41] [PM-21600] Migrate account and security to standalone (#14762) Migrates account and security settings components to standalone and removing them from the `LooseComponentsModule`. --- .../settings/account/account.component.ts | 15 ++++++-- .../account/change-avatar-dialog.component.ts | 7 +++- .../account/change-email.component.spec.ts | 3 +- .../account/change-email.component.ts | 5 ++- .../settings/account/danger-zone.component.ts | 4 +-- .../account/deauthorize-sessions.component.ts | 7 ++-- .../delete-account-dialog.component.ts | 6 +++- .../settings/account/profile.component.ts | 7 +++- .../account/selectable-avatar.component.ts | 6 +++- .../settings/security/api-key.component.ts | 7 ++-- .../security/security-keys.component.ts | 7 ++-- .../settings/security/security.component.ts | 7 ++-- .../src/app/shared/loose-components.module.ts | 35 ------------------- 13 files changed, 62 insertions(+), 54 deletions(-) diff --git a/apps/web/src/app/auth/settings/account/account.component.ts b/apps/web/src/app/auth/settings/account/account.component.ts index cfc01f17674..c06df56e386 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -8,16 +8,27 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { DialogService } from "@bitwarden/components"; +import { HeaderModule } from "../../../layouts/header/header.module"; +import { SharedModule } from "../../../shared"; import { PurgeVaultComponent } from "../../../vault/settings/purge-vault.component"; +import { ChangeEmailComponent } from "./change-email.component"; +import { DangerZoneComponent } from "./danger-zone.component"; import { DeauthorizeSessionsComponent } from "./deauthorize-sessions.component"; import { DeleteAccountDialogComponent } from "./delete-account-dialog.component"; +import { ProfileComponent } from "./profile.component"; import { SetAccountVerifyDevicesDialogComponent } from "./set-account-verify-devices-dialog.component"; @Component({ - selector: "app-account", templateUrl: "account.component.html", - standalone: false, + standalone: true, + imports: [ + SharedModule, + HeaderModule, + ProfileComponent, + ChangeEmailComponent, + DangerZoneComponent, + ], }) export class AccountComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts index 5d71333c0de..80fdb20954f 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts @@ -24,6 +24,10 @@ import { ToastService, } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + +import { SelectableAvatarComponent } from "./selectable-avatar.component"; + type ChangeAvatarDialogData = { profile: ProfileResponse; }; @@ -31,7 +35,8 @@ type ChangeAvatarDialogData = { @Component({ templateUrl: "change-avatar-dialog.component.html", encapsulation: ViewEncapsulation.None, - standalone: false, + standalone: true, + imports: [SharedModule, SelectableAvatarComponent], }) export class ChangeAvatarDialogComponent implements OnInit, OnDestroy { profile: ProfileResponse; diff --git a/apps/web/src/app/auth/settings/account/change-email.component.spec.ts b/apps/web/src/app/auth/settings/account/change-email.component.spec.ts index 838a50b5c2e..f5c0733e5b0 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.spec.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.spec.ts @@ -33,8 +33,7 @@ describe("ChangeEmailComponent", () => { accountService = mockAccountServiceWith("UserId" as UserId); await TestBed.configureTestingModule({ - declarations: [ChangeEmailComponent], - imports: [ReactiveFormsModule, SharedModule], + imports: [ReactiveFormsModule, SharedModule, ChangeEmailComponent], providers: [ { provide: AccountService, useValue: accountService }, { provide: ApiService, useValue: apiService }, diff --git a/apps/web/src/app/auth/settings/account/change-email.component.ts b/apps/web/src/app/auth/settings/account/change-email.component.ts index c86c8c2f4f7..98f704d6044 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.ts @@ -14,10 +14,13 @@ import { UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; +import { SharedModule } from "../../../shared"; + @Component({ selector: "app-change-email", templateUrl: "change-email.component.html", - standalone: false, + standalone: true, + imports: [SharedModule], }) export class ChangeEmailComponent implements OnInit { tokenSent = false; diff --git a/apps/web/src/app/auth/settings/account/danger-zone.component.ts b/apps/web/src/app/auth/settings/account/danger-zone.component.ts index 91f22c7d08f..e07b6e6b8db 100644 --- a/apps/web/src/app/auth/settings/account/danger-zone.component.ts +++ b/apps/web/src/app/auth/settings/account/danger-zone.component.ts @@ -3,8 +3,8 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { JslibModule } from "@bitwarden/angular/jslib.module"; import { TypographyModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; /** * Component for the Danger Zone section of the Account/Organization Settings page. @@ -13,6 +13,6 @@ import { TypographyModule } from "@bitwarden/components"; selector: "app-danger-zone", templateUrl: "danger-zone.component.html", standalone: true, - imports: [TypographyModule, JslibModule, CommonModule], + imports: [CommonModule, TypographyModule, I18nPipe], }) export class DangerZoneComponent {} diff --git a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts index da4d2dce9d7..a48e968ab3e 100644 --- a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts +++ b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts @@ -1,6 +1,7 @@ import { Component } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { Verification } from "@bitwarden/common/auth/types/verification"; @@ -9,10 +10,12 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + @Component({ - selector: "app-deauthorize-sessions", templateUrl: "deauthorize-sessions.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, UserVerificationFormInputComponent], }) export class DeauthorizeSessionsComponent { deauthForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts index 8a3575af5ba..0fc2276b779 100644 --- a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts @@ -3,15 +3,19 @@ import { Component } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; import { Verification } from "@bitwarden/common/auth/types/verification"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + @Component({ templateUrl: "delete-account-dialog.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, UserVerificationFormInputComponent], }) export class DeleteAccountDialogComponent { deleteForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index dc3997f58bb..a33efd742aa 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -14,12 +14,17 @@ import { ProfileResponse } from "@bitwarden/common/models/response/profile.respo import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { DynamicAvatarComponent } from "../../../components/dynamic-avatar.component"; +import { SharedModule } from "../../../shared"; +import { AccountFingerprintComponent } from "../../../shared/components/account-fingerprint/account-fingerprint.component"; + import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; @Component({ selector: "app-profile", templateUrl: "profile.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, DynamicAvatarComponent, AccountFingerprintComponent], }) export class ProfileComponent implements OnInit, OnDestroy { loading = true; diff --git a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts index 33c307882c5..a53e3990090 100644 --- a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts +++ b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts @@ -1,7 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgClass } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { AvatarModule } from "@bitwarden/components"; + @Component({ selector: "selectable-avatar", template: ` `, - standalone: false, + standalone: true, + imports: [NgClass, AvatarModule], }) export class SelectableAvatarComponent { @Input() id: string; diff --git a/apps/web/src/app/auth/settings/security/api-key.component.ts b/apps/web/src/app/auth/settings/security/api-key.component.ts index 4f87c082881..5e61b4b4584 100644 --- a/apps/web/src/app/auth/settings/security/api-key.component.ts +++ b/apps/web/src/app/auth/settings/security/api-key.component.ts @@ -3,12 +3,15 @@ import { Component, Inject } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; +import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { ApiKeyResponse } from "@bitwarden/common/auth/models/response/api-key.response"; import { Verification } from "@bitwarden/common/auth/types/verification"; import { DIALOG_DATA, DialogConfig, DialogService } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + export type ApiKeyDialogData = { keyType: string; isRotation?: boolean; @@ -21,9 +24,9 @@ export type ApiKeyDialogData = { apiKeyDescription: string; }; @Component({ - selector: "app-api-key", templateUrl: "api-key.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, UserVerificationFormInputComponent], }) export class ApiKeyComponent { clientId: string; diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.ts b/apps/web/src/app/auth/settings/security/security-keys.component.ts index 98e743f57dc..6d33193cdde 100644 --- a/apps/web/src/app/auth/settings/security/security-keys.component.ts +++ b/apps/web/src/app/auth/settings/security/security-keys.component.ts @@ -8,12 +8,15 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { DialogService } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + import { ApiKeyComponent } from "./api-key.component"; +import { ChangeKdfModule } from "./change-kdf/change-kdf.module"; @Component({ - selector: "app-security-keys", templateUrl: "security-keys.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, ChangeKdfModule], }) export class SecurityKeysComponent implements OnInit { showChangeKdf = true; diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index 41b1af17abb..95733d693e2 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -4,10 +4,13 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { HeaderModule } from "../../../layouts/header/header.module"; +import { SharedModule } from "../../../shared"; + @Component({ - selector: "app-security", templateUrl: "security.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, HeaderModule], }) export class SecurityComponent implements OnInit { showChangePassword = true; diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index e59633ee499..44323614f17 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -15,23 +15,12 @@ import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations import { RecoverDeleteComponent } from "../auth/recover-delete.component"; import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component"; import { SetPasswordComponent } from "../auth/set-password.component"; -import { AccountComponent } from "../auth/settings/account/account.component"; -import { ChangeAvatarDialogComponent } from "../auth/settings/account/change-avatar-dialog.component"; -import { ChangeEmailComponent } from "../auth/settings/account/change-email.component"; import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component"; -import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component"; -import { DeleteAccountDialogComponent } from "../auth/settings/account/delete-account-dialog.component"; -import { ProfileComponent } from "../auth/settings/account/profile.component"; -import { SelectableAvatarComponent } from "../auth/settings/account/selectable-avatar.component"; import { EmergencyAccessConfirmComponent } from "../auth/settings/emergency-access/confirm/emergency-access-confirm.component"; import { EmergencyAccessAddEditComponent } from "../auth/settings/emergency-access/emergency-access-add-edit.component"; import { EmergencyAccessComponent } from "../auth/settings/emergency-access/emergency-access.component"; import { EmergencyAccessTakeoverComponent } from "../auth/settings/emergency-access/takeover/emergency-access-takeover.component"; import { EmergencyAccessViewComponent } from "../auth/settings/emergency-access/view/emergency-access-view.component"; -import { ApiKeyComponent } from "../auth/settings/security/api-key.component"; -import { ChangeKdfModule } from "../auth/settings/security/change-kdf/change-kdf.module"; -import { SecurityKeysComponent } from "../auth/settings/security/security-keys.component"; -import { SecurityComponent } from "../auth/settings/security/security.component"; import { UserVerificationModule } from "../auth/shared/components/user-verification"; import { UpdatePasswordComponent } from "../auth/update-password.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; @@ -39,7 +28,6 @@ import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component" import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component"; import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component"; -import { DynamicAvatarComponent } from "../components/dynamic-avatar.component"; // eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../dirt/reports/pages/organizations/exposed-passwords-report.component"; // eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module @@ -68,8 +56,6 @@ import { SharedModule } from "./shared.module"; imports: [ SharedModule, UserVerificationModule, - ChangeKdfModule, - DynamicAvatarComponent, AccountFingerprintComponent, OrganizationBadgeModule, PipesModule, @@ -85,11 +71,6 @@ import { SharedModule } from "./shared.module"; ], declarations: [ AcceptFamilySponsorshipComponent, - AccountComponent, - ApiKeyComponent, - ChangeEmailComponent, - DeauthorizeSessionsComponent, - DeleteAccountDialogComponent, EmergencyAccessAddEditComponent, EmergencyAccessComponent, EmergencyAccessConfirmComponent, @@ -104,15 +85,10 @@ import { SharedModule } from "./shared.module"; OrgUserConfirmComponent, OrgWeakPasswordsReportComponent, PremiumBadgeComponent, - ProfileComponent, - ChangeAvatarDialogComponent, PurgeVaultComponent, RecoverDeleteComponent, RecoverTwoFactorComponent, RemovePasswordComponent, - SecurityComponent, - SecurityKeysComponent, - SelectableAvatarComponent, SetPasswordComponent, SponsoredFamiliesComponent, FreeBitwardenFamiliesComponent, @@ -125,12 +101,6 @@ import { SharedModule } from "./shared.module"; exports: [ UserVerificationModule, PremiumBadgeComponent, - AccountComponent, - ApiKeyComponent, - ChangeEmailComponent, - DeauthorizeSessionsComponent, - DeleteAccountDialogComponent, - DynamicAvatarComponent, EmergencyAccessAddEditComponent, EmergencyAccessComponent, EmergencyAccessConfirmComponent, @@ -146,15 +116,10 @@ import { SharedModule } from "./shared.module"; OrgUserConfirmComponent, OrgWeakPasswordsReportComponent, PremiumBadgeComponent, - ProfileComponent, - ChangeAvatarDialogComponent, PurgeVaultComponent, RecoverDeleteComponent, RecoverTwoFactorComponent, RemovePasswordComponent, - SecurityComponent, - SecurityKeysComponent, - SelectableAvatarComponent, SetPasswordComponent, SponsoredFamiliesComponent, FreeBitwardenFamiliesComponent, From 45f2104fd8905ff28fe4c6fe570cdf59ae9cb1d3 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 27 May 2025 14:31:27 +0200 Subject: [PATCH 24/41] fix: broken SDK interface (#14959) --- .../src/platform/services/sdk/default-sdk.service.ts | 12 +++++++++++- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 7027b5134a0..8e84642fb99 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -152,7 +152,15 @@ export class DefaultSdkService implements SdkService { const settings = this.toSettings(env); const client = await this.sdkClientFactory.createSdkClient(settings); - await this.initializeClient(client, account, kdfParams, privateKey, userKey, orgKeys); + await this.initializeClient( + userId, + client, + account, + kdfParams, + privateKey, + userKey, + orgKeys, + ); return client; }; @@ -182,6 +190,7 @@ export class DefaultSdkService implements SdkService { } private async initializeClient( + userId: UserId, client: BitwardenClient, account: AccountInfo, kdfParams: KdfConfig, @@ -190,6 +199,7 @@ export class DefaultSdkService implements SdkService { orgKeys: Record | null, ) { await client.crypto().initialize_user_crypto({ + userId, email: account.email, method: { decryptedKey: { decrypted_user_key: userKey.keyB64 } }, kdfParams: diff --git a/package-lock.json b/package-lock.json index 88302755623..ff21acbc20b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@angular/platform-browser": "18.2.13", "@angular/platform-browser-dynamic": "18.2.13", "@angular/router": "18.2.13", - "@bitwarden/sdk-internal": "0.2.0-main.168", + "@bitwarden/sdk-internal": "0.2.0-main.177", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "3.1.0", @@ -4803,9 +4803,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.168", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.168.tgz", - "integrity": "sha512-NU10oqw+GI9oHrh8/i/IC8/7oaYmswqC2E/0Zc56xC3jY7uNgFZgpae7JhyMU6UxzrAjiEqdmGnm+AGWFiPG8w==", + "version": "0.2.0-main.177", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.177.tgz", + "integrity": "sha512-2fp/g0WJDPPrIqrU88QrwoJsZTzoi7S7eCf+Qq0/8x3ImqQyoYJEdHdz06YHjUdS0CzucPrwTo5zJ/ZvcLNOmQ==", "license": "GPL-3.0" }, "node_modules/@bitwarden/send-ui": { diff --git a/package.json b/package.json index 8a8e80bd632..d27d919fb16 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,7 @@ "@angular/platform-browser": "18.2.13", "@angular/platform-browser-dynamic": "18.2.13", "@angular/router": "18.2.13", - "@bitwarden/sdk-internal": "0.2.0-main.168", + "@bitwarden/sdk-internal": "0.2.0-main.177", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "3.1.0", From 888e2031a702f3e12c61b666aa87fa8fc62d3cd4 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 27 May 2025 08:24:53 -0500 Subject: [PATCH 25/41] [PM-21090] Vault - Repeated Syncs (#14740) * move `fullSync` contents to private methods in prep to storing the respective promise * store in-flight sync so multiple calls to the sync service are avoided * Revert "store in-flight sync so multiple calls to the sync service are avoided" This reverts commit 233c8e9d4b92d448f63e157b01c168d840c7e531. * Revert "move `fullSync` contents to private methods in prep to storing the respective promise" This reverts commit 3f686ac6a4b41e332180478f269d9fde85f562a1. * store inflight API calls for sync service - This avoids duplicate network requests in a relatively short amount of time but still allows consumers to call `fullSync` if needed * add debug log for duplicate sync --- .../sync/default-sync.service.spec.ts | 65 +++++++++++++++---- .../src/platform/sync/default-sync.service.ts | 32 ++++++++- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/libs/common/src/platform/sync/default-sync.service.spec.ts b/libs/common/src/platform/sync/default-sync.service.spec.ts index 29543672dbe..fc6b9481bd5 100644 --- a/libs/common/src/platform/sync/default-sync.service.spec.ts +++ b/libs/common/src/platform/sync/default-sync.service.spec.ts @@ -130,23 +130,23 @@ describe("DefaultSyncService", () => { const user1 = "user1" as UserId; + const emptySyncResponse = new SyncResponse({ + profile: { + id: user1, + }, + folders: [], + collections: [], + ciphers: [], + sends: [], + domains: [], + policies: [], + }); + describe("fullSync", () => { beforeEach(() => { accountService.activeAccount$ = of({ id: user1 } as Account); Matrix.autoMockMethod(authService.authStatusFor$, () => of(AuthenticationStatus.Unlocked)); - apiService.getSync.mockResolvedValue( - new SyncResponse({ - profile: { - id: user1, - }, - folders: [], - collections: [], - ciphers: [], - sends: [], - domains: [], - policies: [], - }), - ); + apiService.getSync.mockResolvedValue(emptySyncResponse); Matrix.autoMockMethod(userDecryptionOptionsService.userDecryptionOptionsById$, () => of({ hasMasterPassword: true } satisfies UserDecryptionOptions), ); @@ -201,5 +201,44 @@ describe("DefaultSyncService", () => { expect(apiService.refreshIdentityToken).toHaveBeenCalledTimes(1); expect(apiService.getSync).toHaveBeenCalledTimes(1); }); + + describe("in-flight syncs", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("does not call getSync when one is already in progress", async () => { + const fullSyncPromises = [sut.fullSync(true), sut.fullSync(false), sut.fullSync(false)]; + + jest.advanceTimersByTime(100); + + await Promise.all(fullSyncPromises); + + expect(apiService.getSync).toHaveBeenCalledTimes(1); + }); + + it("does not call refreshIdentityToken when one is already in progress", async () => { + const fullSyncPromises = [sut.fullSync(true), sut.fullSync(false), sut.fullSync(false)]; + + jest.advanceTimersByTime(100); + + await Promise.all(fullSyncPromises); + + expect(apiService.refreshIdentityToken).toHaveBeenCalledTimes(1); + }); + + it("resets the in-flight properties when the complete", async () => { + const fullSyncPromises = [sut.fullSync(true), sut.fullSync(true)]; + + await Promise.all(fullSyncPromises); + + expect(sut["inFlightApiCalls"].refreshToken).toBeNull(); + expect(sut["inFlightApiCalls"].sync).toBeNull(); + }); + }); }); }); diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 6e1a8f28443..47ac3784c33 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -58,11 +58,21 @@ import { MessageSender } from "../messaging"; import { StateProvider } from "../state"; import { CoreSyncService } from "./core-sync.service"; +import { SyncResponse } from "./sync.response"; import { SyncOptions } from "./sync.service"; export class DefaultSyncService extends CoreSyncService { syncInProgress = false; + /** The promises associated with any in-flight api calls. */ + private inFlightApiCalls: { + refreshToken: Promise | null; + sync: Promise | null; + } = { + refreshToken: null, + sync: null, + }; + constructor( private masterPasswordService: InternalMasterPasswordServiceAbstraction, accountService: AccountService, @@ -141,9 +151,24 @@ export class DefaultSyncService extends CoreSyncService { try { if (!skipTokenRefresh) { - await this.apiService.refreshIdentityToken(); + // Store the promise so multiple calls to refresh the token are not made + if (this.inFlightApiCalls.refreshToken === null) { + this.inFlightApiCalls.refreshToken = this.apiService.refreshIdentityToken(); + } + + await this.inFlightApiCalls.refreshToken; } - const response = await this.apiService.getSync(); + + // Store the promise so multiple calls to sync are not made + if (this.inFlightApiCalls.sync === null) { + this.inFlightApiCalls.sync = this.apiService.getSync(); + } else { + this.logService.debug( + "Sync: Sync network call already in progress, returning existing promise", + ); + } + + const response = await this.inFlightApiCalls.sync; await this.syncProfile(response.profile); await this.syncFolders(response.folders, response.profile.id); @@ -162,6 +187,9 @@ export class DefaultSyncService extends CoreSyncService { } else { return this.syncCompleted(false, userId); } + } finally { + this.inFlightApiCalls.refreshToken = null; + this.inFlightApiCalls.sync = null; } } From f4f659c52a0703d9f1e868185925185799f34b5e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 27 May 2025 15:38:33 +0200 Subject: [PATCH 26/41] Increase allowed memory for chromatic (#14964) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d27d919fb16..f63db41e1f3 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "lint:dep-ownership": "tsc --project ./scripts/tsconfig.json && node ./scripts/dist/dep-ownership.js", "docs:json": "compodoc -p ./tsconfig.json -e json -d . --disableRoutesGraph", "storybook": "ng run components:storybook", - "build-storybook": "ng run components:build-storybook", - "build-storybook:ci": "ng run components:build-storybook --webpack-stats-json", + "build-storybook": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" ng run components:build-storybook", + "build-storybook:ci": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" ng run components:build-storybook --webpack-stats-json", "test-stories": "test-storybook --url http://localhost:6006", "test-stories:watch": "test-stories --watch", "postinstall": "patch-package" From 97a591e738b3d062cdf8b75efd8b1ccef8934547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Tue, 27 May 2025 09:51:14 -0400 Subject: [PATCH 27/41] [PM-16793] port credential generator service to providers (#14071) * introduce extension service * deprecate legacy forwarder types * eliminate repeat algorithm emissions * extend logging to preference management * align forwarder ids with vendor ids * fix duplicate policy emissions; debugging required logger enhancements ----- Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- apps/browser/src/_locales/en/messages.json | 3 + apps/desktop/src/locales/en/messages.json | 39 +- .../policies/password-generator.component.ts | 26 +- apps/web/src/locales/en/messages.json | 27 +- .../tools/extension/extension.service.spec.ts | 1 + libs/common/src/tools/log/disabled-logger.ts | 24 + .../src/tools/log/disabled-semantic-logger.ts | 22 - libs/common/src/tools/log/factory.ts | 79 +- libs/common/src/tools/log/index.ts | 2 + libs/common/src/tools/log/types.ts | 11 + libs/common/src/tools/log/util.ts | 12 + libs/common/src/tools/private-classifier.ts | 2 +- libs/common/src/tools/public-classifier.ts | 2 +- libs/common/src/tools/rx.rxjs.ts | 13 + libs/common/src/tools/rx.spec.ts | 1504 +++++++++-------- libs/common/src/tools/rx.ts | 82 +- .../user-state-subject-dependency-provider.ts | 5 + .../tools/state/user-state-subject.spec.ts | 1 + .../src/tools/state/user-state-subject.ts | 7 +- libs/common/src/tools/types.ts | 5 + libs/common/src/tools/util.ts | 16 +- .../src/components/export.component.ts | 6 +- .../src/catchall-settings.component.ts | 19 +- .../credential-generator-history.component.ts | 19 +- .../src/credential-generator.component.html | 10 +- .../src/credential-generator.component.ts | 300 ++-- .../src/forwarder-settings.component.ts | 52 +- .../src/generator-services.module.ts | 144 +- .../src/passphrase-settings.component.ts | 88 +- .../src/password-generator.component.html | 5 +- .../src/password-generator.component.ts | 142 +- .../src/password-settings.component.ts | 86 +- .../src/subaddress-settings.component.ts | 20 +- .../src/username-generator.component.html | 6 +- .../src/username-generator.component.ts | 286 ++-- .../src/username-settings.component.ts | 19 +- libs/tools/generator/components/src/util.ts | 103 +- ...redential-generator-service.abstraction.ts | 104 ++ .../generator.service.abstraction.ts | 1 + .../generator/core/src/abstractions/index.ts | 1 + .../core/src/data/default-addy-io-options.ts | 8 - .../data/default-credential-preferences.ts | 18 - .../src/data/default-duck-duck-go-options.ts | 6 - .../core/src/data/default-fastmail-options.ts | 8 - .../src/data/default-firefox-relay-options.ts | 6 - .../src/data/default-forward-email-options.ts | 7 - .../src/data/default-simple-login-options.ts | 7 - .../core/src/data/generator-types.ts | 15 - .../generator/core/src/data/generators.ts | 422 ----- libs/tools/generator/core/src/data/index.ts | 12 - .../tools/generator/core/src/data/policies.ts | 23 - .../core/src/data/username-digits.ts | 4 - .../core/src/engine/email-randomizer.spec.ts | 21 +- .../core/src/engine/email-randomizer.ts | 5 +- .../generator/core/src/engine/forwarder.ts | 4 +- .../src/engine/password-randomizer.spec.ts | 11 +- .../core/src/engine/password-randomizer.ts | 5 +- .../src/engine/username-randomizer.spec.ts | 8 +- libs/tools/generator/core/src/index.ts | 23 +- .../generator/core/src/integration/addy-io.ts | 3 +- .../core/src/integration/duck-duck-go.ts | 3 +- .../core/src/integration/fastmail.ts | 3 +- .../core/src/integration/firefox-relay.ts | 3 +- .../core/src/integration/forward-email.ts | 3 +- .../core/src/integration/simple-login.ts | 3 +- .../core/src/metadata/algorithm-metadata.ts | 10 +- .../core/src/metadata/email/catchall.spec.ts | 3 +- .../core/src/metadata/email/catchall.ts | 9 +- .../core/src/metadata/email/forwarder.ts | 17 +- .../src/metadata/email/plus-address.spec.ts | 3 +- .../core/src/metadata/email/plus-address.ts | 9 +- .../core/src/metadata/generator-metadata.ts | 3 +- .../generator/core/src/metadata/index.ts | 42 +- .../metadata/password/eff-word-list.spec.ts | 3 +- .../src/metadata/password/eff-word-list.ts | 11 +- .../metadata/password/random-password.spec.ts | 3 +- .../src/metadata/password/random-password.ts | 9 +- .../metadata/username/eff-word-list.spec.ts | 3 +- .../src/metadata/username/eff-word-list.ts | 9 +- .../tools/generator/core/src/metadata/util.ts | 8 +- .../available-algorithms-constraint.ts | 76 + .../available-algorithms-policy.spec.ts | 18 +- .../policies/available-algorithms-policy.ts | 43 +- ...ynamic-password-policy-constraints.spec.ts | 50 +- .../generator/core/src/policies/index.ts | 2 + ...phrase-generator-options-evaluator.spec.ts | 70 +- .../passphrase-least-privilege.spec.ts | 20 +- .../passphrase-policy-constraints.spec.ts | 20 +- ...ssword-generator-options-evaluator.spec.ts | 126 +- .../policies/password-least-privilege.spec.ts | 24 +- .../credential-generator-providers.ts | 14 + .../providers/credential-preferences.spec.ts | 105 ++ .../src/providers/credential-preferences.ts | 28 + .../generator-dependency-provider.ts | 12 + .../generator-metadata-provider.spec.ts | 5 +- .../generator-metadata-provider.ts | 56 +- .../generator-profile-provider.spec.ts | 1 + .../generator-profile-provider.ts | 6 +- .../generator/core/src/providers/index.ts | 4 + libs/tools/generator/core/src/rx.ts | 14 - .../credential-generator.service.spec.ts | 1050 ------------ .../services/credential-generator.service.ts | 296 ---- .../services/credential-preferences.spec.ts | 53 - .../src/services/credential-preferences.ts | 30 - ...fault-credential-generator.service.spec.ts | 356 ++++ .../default-credential-generator.service.ts | 219 +++ .../default-generator.service.spec.ts | 12 +- .../generator/core/src/services/index.ts | 2 +- .../eff-username-generator-strategy.ts | 7 +- .../passphrase-generator-strategy.spec.ts | 31 +- .../passphrase-generator-strategy.ts | 14 +- .../password-generator-strategy.spec.ts | 53 +- .../strategies/password-generator-strategy.ts | 18 +- .../core/src/types/algorithm-info.ts | 50 + .../credential-generator-configuration.ts | 153 -- .../core/src/types/credential-preference.ts | 10 + .../core/src/types/forwarder-options.ts | 65 +- .../core/src/types/generate-request.ts | 11 +- .../src/types/generated-credential.spec.ts | 20 +- .../core/src/types/generated-credential.ts | 8 +- .../core/src/types/generator-type.ts | 83 - libs/tools/generator/core/src/types/index.ts | 16 +- .../core/src/types/policy-configuration.ts | 13 - libs/tools/generator/core/src/util.spec.ts | 90 +- libs/tools/generator/core/src/util.ts | 33 +- libs/tools/generator/core/tsconfig.json | 6 +- .../history/src/generated-credential.spec.ts | 22 +- .../history/src/generated-credential.ts | 4 +- .../src/generator-history.abstraction.ts | 4 +- .../local-generator-history.service.spec.ts | 54 +- .../src/local-generator-history.service.ts | 9 +- .../legacy/src}/forwarders.ts | 16 +- ...legacy-password-generation.service.spec.ts | 89 +- ...legacy-username-generation.service.spec.ts | 103 +- .../src/legacy-username-generation.service.ts | 19 +- .../generator-navigation-evaluator.spec.ts | 4 +- .../src/generator-navigation-evaluator.ts | 4 +- .../src/generator-navigation-policy.ts | 4 +- .../navigation/src/generator-navigation.ts | 7 +- .../options/send-options.component.ts | 6 +- 140 files changed, 3720 insertions(+), 4085 deletions(-) create mode 100644 libs/common/src/tools/log/disabled-logger.ts delete mode 100644 libs/common/src/tools/log/disabled-semantic-logger.ts create mode 100644 libs/common/src/tools/log/types.ts create mode 100644 libs/common/src/tools/log/util.ts create mode 100644 libs/common/src/tools/rx.rxjs.ts create mode 100644 libs/tools/generator/core/src/abstractions/credential-generator-service.abstraction.ts delete mode 100644 libs/tools/generator/core/src/data/default-addy-io-options.ts delete mode 100644 libs/tools/generator/core/src/data/default-credential-preferences.ts delete mode 100644 libs/tools/generator/core/src/data/default-duck-duck-go-options.ts delete mode 100644 libs/tools/generator/core/src/data/default-fastmail-options.ts delete mode 100644 libs/tools/generator/core/src/data/default-firefox-relay-options.ts delete mode 100644 libs/tools/generator/core/src/data/default-forward-email-options.ts delete mode 100644 libs/tools/generator/core/src/data/default-simple-login-options.ts delete mode 100644 libs/tools/generator/core/src/data/generator-types.ts delete mode 100644 libs/tools/generator/core/src/data/generators.ts delete mode 100644 libs/tools/generator/core/src/data/policies.ts delete mode 100644 libs/tools/generator/core/src/data/username-digits.ts create mode 100644 libs/tools/generator/core/src/policies/available-algorithms-constraint.ts create mode 100644 libs/tools/generator/core/src/providers/credential-generator-providers.ts create mode 100644 libs/tools/generator/core/src/providers/credential-preferences.spec.ts create mode 100644 libs/tools/generator/core/src/providers/credential-preferences.ts create mode 100644 libs/tools/generator/core/src/providers/generator-dependency-provider.ts rename libs/tools/generator/core/src/{services => providers}/generator-metadata-provider.spec.ts (98%) rename libs/tools/generator/core/src/{services => providers}/generator-metadata-provider.ts (87%) rename libs/tools/generator/core/src/{services => providers}/generator-profile-provider.spec.ts (99%) rename libs/tools/generator/core/src/{services => providers}/generator-profile-provider.ts (94%) create mode 100644 libs/tools/generator/core/src/providers/index.ts delete mode 100644 libs/tools/generator/core/src/services/credential-generator.service.spec.ts delete mode 100644 libs/tools/generator/core/src/services/credential-generator.service.ts delete mode 100644 libs/tools/generator/core/src/services/credential-preferences.spec.ts delete mode 100644 libs/tools/generator/core/src/services/credential-preferences.ts create mode 100644 libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts create mode 100644 libs/tools/generator/core/src/services/default-credential-generator.service.ts create mode 100644 libs/tools/generator/core/src/types/algorithm-info.ts delete mode 100644 libs/tools/generator/core/src/types/credential-generator-configuration.ts create mode 100644 libs/tools/generator/core/src/types/credential-preference.ts delete mode 100644 libs/tools/generator/core/src/types/generator-type.ts rename libs/tools/generator/{core/src/data => extensions/legacy/src}/forwarders.ts (73%) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 4775d1f7af0..feb5a7706f3 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 631f7d571f6..9d668d464ae 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts index f11b14aea38..26f87f333eb 100644 --- a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts @@ -7,7 +7,7 @@ import { BehaviorSubject, map } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Generators } from "@bitwarden/generator-core"; +import { BuiltIn, Profile } from "@bitwarden/generator-core"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; @@ -26,14 +26,22 @@ export class PasswordGeneratorPolicy extends BasePolicy { export class PasswordGeneratorPolicyComponent extends BasePolicyComponent { // these properties forward the application default settings to the UI // for HTML attribute bindings - protected readonly minLengthMin = Generators.password.settings.constraints.length.min; - protected readonly minLengthMax = Generators.password.settings.constraints.length.max; - protected readonly minNumbersMin = Generators.password.settings.constraints.minNumber.min; - protected readonly minNumbersMax = Generators.password.settings.constraints.minNumber.max; - protected readonly minSpecialMin = Generators.password.settings.constraints.minSpecial.min; - protected readonly minSpecialMax = Generators.password.settings.constraints.minSpecial.max; - protected readonly minNumberWordsMin = Generators.passphrase.settings.constraints.numWords.min; - protected readonly minNumberWordsMax = Generators.passphrase.settings.constraints.numWords.max; + protected readonly minLengthMin = + BuiltIn.password.profiles[Profile.account].constraints.default.length.min; + protected readonly minLengthMax = + BuiltIn.password.profiles[Profile.account].constraints.default.length.max; + protected readonly minNumbersMin = + BuiltIn.password.profiles[Profile.account].constraints.default.minNumber.min; + protected readonly minNumbersMax = + BuiltIn.password.profiles[Profile.account].constraints.default.minNumber.max; + protected readonly minSpecialMin = + BuiltIn.password.profiles[Profile.account].constraints.default.minSpecial.min; + protected readonly minSpecialMax = + BuiltIn.password.profiles[Profile.account].constraints.default.minSpecial.max; + protected readonly minNumberWordsMin = + BuiltIn.passphrase.profiles[Profile.account].constraints.default.numWords.min; + protected readonly minNumberWordsMax = + BuiltIn.passphrase.profiles[Profile.account].constraints.default.numWords.max; data = this.formBuilder.group({ overridePasswordType: [null], diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 3c17baabbb9..a170612fab2 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6825,6 +6819,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6888,6 +6900,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/libs/common/src/tools/extension/extension.service.spec.ts b/libs/common/src/tools/extension/extension.service.spec.ts index dad5684b523..9959488feca 100644 --- a/libs/common/src/tools/extension/extension.service.spec.ts +++ b/libs/common/src/tools/extension/extension.service.spec.ts @@ -60,6 +60,7 @@ const SomeProvider = { } as LegacyEncryptorProvider, state: SomeStateProvider, log: disabledSemanticLoggerProvider, + now: Date.now, } as UserStateSubjectDependencyProvider; const SomeExtension: ExtensionMetadata = { diff --git a/libs/common/src/tools/log/disabled-logger.ts b/libs/common/src/tools/log/disabled-logger.ts new file mode 100644 index 00000000000..53feb0c8b39 --- /dev/null +++ b/libs/common/src/tools/log/disabled-logger.ts @@ -0,0 +1,24 @@ +import { Jsonify } from "type-fest"; + +import { deepFreeze } from "../util"; + +import { SemanticLogger } from "./semantic-logger.abstraction"; + +/** All disabled loggers emitted by this module are `===` to this logger. */ +export const DISABLED_LOGGER: SemanticLogger = deepFreeze({ + debug(_content: Jsonify, _message?: string): void {}, + + info(_content: Jsonify, _message?: string): void {}, + + warn(_content: Jsonify, _message?: string): void {}, + + error(_content: Jsonify, _message?: string): void {}, + + panic(content: Jsonify, message?: string): never { + if (typeof content === "string" && !message) { + throw new Error(content); + } else { + throw new Error(message); + } + }, +}); diff --git a/libs/common/src/tools/log/disabled-semantic-logger.ts b/libs/common/src/tools/log/disabled-semantic-logger.ts deleted file mode 100644 index 21ea48bbe51..00000000000 --- a/libs/common/src/tools/log/disabled-semantic-logger.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Jsonify } from "type-fest"; - -import { SemanticLogger } from "./semantic-logger.abstraction"; - -/** Disables semantic logs. Still panics. */ -export class DisabledSemanticLogger implements SemanticLogger { - debug(_content: Jsonify, _message?: string): void {} - - info(_content: Jsonify, _message?: string): void {} - - warn(_content: Jsonify, _message?: string): void {} - - error(_content: Jsonify, _message?: string): void {} - - panic(content: Jsonify, message?: string): never { - if (typeof content === "string" && !message) { - throw new Error(content); - } else { - throw new Error(message); - } - } -} diff --git a/libs/common/src/tools/log/factory.ts b/libs/common/src/tools/log/factory.ts index f8abc4d2240..5f0f2d74e91 100644 --- a/libs/common/src/tools/log/factory.ts +++ b/libs/common/src/tools/log/factory.ts @@ -3,11 +3,10 @@ import { Jsonify } from "type-fest"; import { LogService } from "../../platform/abstractions/log.service"; import { DefaultSemanticLogger } from "./default-semantic-logger"; -import { DisabledSemanticLogger } from "./disabled-semantic-logger"; +import { DISABLED_LOGGER } from "./disabled-logger"; import { SemanticLogger } from "./semantic-logger.abstraction"; - -/** A type for injection of a log provider */ -export type LogProvider = (context: Jsonify) => SemanticLogger; +import { LogProvider } from "./types"; +import { warnLoggingEnabled } from "./util"; /** Instantiates a semantic logger that emits nothing when a message * is logged. @@ -18,38 +17,72 @@ export type LogProvider = (context: Jsonify) => SemanticLogger export function disabledSemanticLoggerProvider( _context: Jsonify, ): SemanticLogger { - return new DisabledSemanticLogger(); + return DISABLED_LOGGER; } /** Instantiates a semantic logger that emits logs to the console. - * @param context a static payload that is cloned when the logger - * logs a message. The `messages`, `level`, and `content` fields - * are reserved for use by loggers. - * @param settings specializes how the semantic logger functions. - * If this is omitted, the logger suppresses debug messages. + * @param logService writes semantic logs to the console */ -export function consoleSemanticLoggerProvider( - logger: LogService, - context: Jsonify, -): SemanticLogger { - return new DefaultSemanticLogger(logger, context); +export function consoleSemanticLoggerProvider(logService: LogService): LogProvider { + function provider(context: Jsonify) { + const logger = new DefaultSemanticLogger(logService, context); + + warnLoggingEnabled(logService, "consoleSemanticLoggerProvider", context); + return logger; + } + + return provider; } -/** Instantiates a semantic logger that emits logs to the console. +/** Instantiates a semantic logger that emits logs to the console when the + * context's `type` matches its values. + * @param logService writes semantic logs to the console + * @param types the values to match against + */ +export function enableLogForTypes(logService: LogService, types: string[]): LogProvider { + if (types.length) { + warnLoggingEnabled(logService, "enableLogForTypes", { types }); + } + + function provider(context: Jsonify) { + const { type } = context as { type?: unknown }; + if (typeof type === "string" && types.includes(type)) { + const logger = new DefaultSemanticLogger(logService, context); + + warnLoggingEnabled(logService, "enableLogForTypes", { + targetType: type, + available: types, + loggerContext: context, + }); + return logger; + } else { + return DISABLED_LOGGER; + } + } + + return provider; +} + +/** Instantiates a semantic logger that emits logs to the console when its enabled. + * @param enable logs are emitted when this is true + * @param logService writes semantic logs to the console * @param context a static payload that is cloned when the logger - * logs a message. The `messages`, `level`, and `content` fields - * are reserved for use by loggers. - * @param settings specializes how the semantic logger functions. - * If this is omitted, the logger suppresses debug messages. + * logs a message. + * + * @remarks The `message`, `level`, `provider`, and `content` fields + * are reserved for use by the semantic logging system. */ export function ifEnabledSemanticLoggerProvider( enable: boolean, - logger: LogService, + logService: LogService, context: Jsonify, ) { if (enable) { - return consoleSemanticLoggerProvider(logger, context); + const logger = new DefaultSemanticLogger(logService, context); + + warnLoggingEnabled(logService, "ifEnabledSemanticLoggerProvider", context); + return logger; } else { - return disabledSemanticLoggerProvider(context); + return DISABLED_LOGGER; } } diff --git a/libs/common/src/tools/log/index.ts b/libs/common/src/tools/log/index.ts index 22444d23e27..1c93a6ce63f 100644 --- a/libs/common/src/tools/log/index.ts +++ b/libs/common/src/tools/log/index.ts @@ -1,2 +1,4 @@ export * from "./factory"; +export * from "./disabled-logger"; +export { LogProvider } from "./types"; export { SemanticLogger } from "./semantic-logger.abstraction"; diff --git a/libs/common/src/tools/log/types.ts b/libs/common/src/tools/log/types.ts new file mode 100644 index 00000000000..ca887af4225 --- /dev/null +++ b/libs/common/src/tools/log/types.ts @@ -0,0 +1,11 @@ +import { Jsonify } from "type-fest"; + +import { SemanticLogger } from "./semantic-logger.abstraction"; + +/** Creates a semantic logger. + * @param context all logs emitted by the logger are extended with + * these fields. + * @remarks The `message`, `level`, `provider`, and `content` fields + * are reserved for use by the semantic logging system. + */ +export type LogProvider = (context: Jsonify) => SemanticLogger; diff --git a/libs/common/src/tools/log/util.ts b/libs/common/src/tools/log/util.ts new file mode 100644 index 00000000000..cf1c39230e1 --- /dev/null +++ b/libs/common/src/tools/log/util.ts @@ -0,0 +1,12 @@ +import { LogService } from "../../platform/abstractions/log.service"; + +// show our GRIT - these functions implement generalized logging +// controls and should return DISABLED_LOGGER in production. +export function warnLoggingEnabled(logService: LogService, method: string, context?: any) { + logService.warning({ + method, + context, + provider: "tools/log", + message: "Semantic logging enabled. 🦟 Please report this bug if you see it 🦟", + }); +} diff --git a/libs/common/src/tools/private-classifier.ts b/libs/common/src/tools/private-classifier.ts index e2406d314c0..58244ae9906 100644 --- a/libs/common/src/tools/private-classifier.ts +++ b/libs/common/src/tools/private-classifier.ts @@ -17,7 +17,7 @@ export class PrivateClassifier implements Classifier; - return { disclosed: {}, secret }; + return { disclosed: null, secret }; } declassify(_disclosed: Jsonify>, secret: Jsonify) { diff --git a/libs/common/src/tools/public-classifier.ts b/libs/common/src/tools/public-classifier.ts index 136bee555ac..e036ebd1c42 100644 --- a/libs/common/src/tools/public-classifier.ts +++ b/libs/common/src/tools/public-classifier.ts @@ -16,7 +16,7 @@ export class PublicClassifier implements Classifier; - return { disclosed, secret: "" }; + return { disclosed, secret: null }; } declassify(disclosed: Jsonify, _secret: Jsonify>) { diff --git a/libs/common/src/tools/rx.rxjs.ts b/libs/common/src/tools/rx.rxjs.ts new file mode 100644 index 00000000000..7c11d658f19 --- /dev/null +++ b/libs/common/src/tools/rx.rxjs.ts @@ -0,0 +1,13 @@ +import { Observable } from "rxjs"; + +/** + * Used to infer types from arguments to functions like {@link withLatestReady}. + * So that you can have `forkJoin([Observable, PromiseLike]): Observable<[A, B]>` + * et al. + * @remarks this type definition is derived from rxjs' {@link ObservableInputTuple}. + * The difference is it *only* works with observables, while the rx version works + * with any thing that can become an observable. + */ +export type ObservableTuple = { + [K in keyof T]: Observable; +}; diff --git a/libs/common/src/tools/rx.spec.ts b/libs/common/src/tools/rx.spec.ts index ee1de1c9118..5177dcaa2a5 100644 --- a/libs/common/src/tools/rx.spec.ts +++ b/libs/common/src/tools/rx.spec.ts @@ -3,7 +3,7 @@ * @jest-environment ../../../../shared/test.environment.ts */ // @ts-strict-ignore this file explicitly tests what happens when types are ignored -import { of, firstValueFrom, Subject, tap, EmptyError } from "rxjs"; +import { of, firstValueFrom, Subject, tap, EmptyError, BehaviorSubject } from "rxjs"; import { awaitAsync, trackEmissions } from "../../spec"; @@ -16,733 +16,825 @@ import { reduceCollection, withLatestReady, pin, + memoizedMap, } from "./rx"; -describe("errorOnChange", () => { - it("emits a single value when the input emits only once", async () => { - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(errorOnChange()).subscribe((v) => results.push(v)); +describe("tools rx utilites", () => { + describe("errorOnChange", () => { + it("emits a single value when the input emits only once", async () => { + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(errorOnChange()).subscribe((v) => results.push(v)); - source$.next(1); + source$.next(1); - expect(results).toEqual([1]); + expect(results).toEqual([1]); + }); + + it("emits when the input emits", async () => { + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(errorOnChange()).subscribe((v) => results.push(v)); + + source$.next(1); + source$.next(1); + + expect(results).toEqual([1, 1]); + }); + + it("errors when the input errors", async () => { + const source$ = new Subject(); + const expected = {}; + let error: any = null; + source$.pipe(errorOnChange()).subscribe({ error: (v: unknown) => (error = v) }); + + source$.error(expected); + + expect(error).toBe(expected); + }); + + it("completes when the input completes", async () => { + const source$ = new Subject(); + let complete: boolean = false; + source$.pipe(errorOnChange()).subscribe({ complete: () => (complete = true) }); + + source$.complete(); + + expect(complete).toBe(true); + }); + + it("errors when the input changes", async () => { + const source$ = new Subject(); + let error: any = null; + source$.pipe(errorOnChange()).subscribe({ error: (v: unknown) => (error = v) }); + + source$.next(1); + source$.next(2); + + expect(error).toEqual({ expectedValue: 1, actualValue: 2 }); + }); + + it("emits when the extracted value remains constant", async () => { + type Foo = { foo: string }; + const source$ = new Subject(); + const results: Foo[] = []; + source$.pipe(errorOnChange((v) => v.foo)).subscribe((v) => results.push(v)); + + source$.next({ foo: "bar" }); + source$.next({ foo: "bar" }); + + expect(results).toEqual([{ foo: "bar" }, { foo: "bar" }]); + }); + + it("errors when an extracted value changes", async () => { + type Foo = { foo: string }; + const source$ = new Subject(); + let error: any = null; + source$.pipe(errorOnChange((v) => v.foo)).subscribe({ error: (v: unknown) => (error = v) }); + + source$.next({ foo: "bar" }); + source$.next({ foo: "baz" }); + + expect(error).toEqual({ expectedValue: "bar", actualValue: "baz" }); + }); + + it("constructs an error when the extracted value changes", async () => { + type Foo = { foo: string }; + const source$ = new Subject(); + let error: any = null; + source$ + .pipe( + errorOnChange( + (v) => v.foo, + (expected, actual) => ({ expected, actual }), + ), + ) + .subscribe({ error: (v: unknown) => (error = v) }); + + source$.next({ foo: "bar" }); + source$.next({ foo: "baz" }); + + expect(error).toEqual({ expected: "bar", actual: "baz" }); + }); }); - it("emits when the input emits", async () => { - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(errorOnChange()).subscribe((v) => results.push(v)); + describe("reduceCollection", () => { + it.each([[null], [undefined], [[]]])( + "should return the default value when the collection is %p", + async (value: number[]) => { + const reduce = (acc: number, value: number) => acc + value; + const source$ = of(value); - source$.next(1); - source$.next(1); + const result$ = source$.pipe(reduceCollection(reduce, 100)); + const result = await firstValueFrom(result$); - expect(results).toEqual([1, 1]); - }); + expect(result).toEqual(100); + }, + ); - it("errors when the input errors", async () => { - const source$ = new Subject(); - const expected = {}; - let error: any = null; - source$.pipe(errorOnChange()).subscribe({ error: (v: unknown) => (error = v) }); - - source$.error(expected); - - expect(error).toBe(expected); - }); - - it("completes when the input completes", async () => { - const source$ = new Subject(); - let complete: boolean = false; - source$.pipe(errorOnChange()).subscribe({ complete: () => (complete = true) }); - - source$.complete(); - - expect(complete).toBe(true); - }); - - it("errors when the input changes", async () => { - const source$ = new Subject(); - let error: any = null; - source$.pipe(errorOnChange()).subscribe({ error: (v: unknown) => (error = v) }); - - source$.next(1); - source$.next(2); - - expect(error).toEqual({ expectedValue: 1, actualValue: 2 }); - }); - - it("emits when the extracted value remains constant", async () => { - type Foo = { foo: string }; - const source$ = new Subject(); - const results: Foo[] = []; - source$.pipe(errorOnChange((v) => v.foo)).subscribe((v) => results.push(v)); - - source$.next({ foo: "bar" }); - source$.next({ foo: "bar" }); - - expect(results).toEqual([{ foo: "bar" }, { foo: "bar" }]); - }); - - it("errors when an extracted value changes", async () => { - type Foo = { foo: string }; - const source$ = new Subject(); - let error: any = null; - source$.pipe(errorOnChange((v) => v.foo)).subscribe({ error: (v: unknown) => (error = v) }); - - source$.next({ foo: "bar" }); - source$.next({ foo: "baz" }); - - expect(error).toEqual({ expectedValue: "bar", actualValue: "baz" }); - }); - - it("constructs an error when the extracted value changes", async () => { - type Foo = { foo: string }; - const source$ = new Subject(); - let error: any = null; - source$ - .pipe( - errorOnChange( - (v) => v.foo, - (expected, actual) => ({ expected, actual }), - ), - ) - .subscribe({ error: (v: unknown) => (error = v) }); - - source$.next({ foo: "bar" }); - source$.next({ foo: "baz" }); - - expect(error).toEqual({ expected: "bar", actual: "baz" }); - }); -}); - -describe("reduceCollection", () => { - it.each([[null], [undefined], [[]]])( - "should return the default value when the collection is %p", - async (value: number[]) => { + it("should reduce the collection to a single value", async () => { const reduce = (acc: number, value: number) => acc + value; - const source$ = of(value); + const source$ = of([1, 2, 3]); - const result$ = source$.pipe(reduceCollection(reduce, 100)); + const result$ = source$.pipe(reduceCollection(reduce, 0)); const result = await firstValueFrom(result$); - expect(result).toEqual(100); - }, - ); - - it("should reduce the collection to a single value", async () => { - const reduce = (acc: number, value: number) => acc + value; - const source$ = of([1, 2, 3]); - - const result$ = source$.pipe(reduceCollection(reduce, 0)); - const result = await firstValueFrom(result$); - - expect(result).toEqual(6); - }); -}); - -describe("distinctIfShallowMatch", () => { - it("emits a single value", async () => { - const source$ = of({ foo: true }); - const pipe$ = source$.pipe(distinctIfShallowMatch()); - - const result = trackEmissions(pipe$); - await awaitAsync(); - - expect(result).toEqual([{ foo: true }]); - }); - - it("emits different values", async () => { - const source$ = of({ foo: true }, { foo: false }); - const pipe$ = source$.pipe(distinctIfShallowMatch()); - - const result = trackEmissions(pipe$); - await awaitAsync(); - - expect(result).toEqual([{ foo: true }, { foo: false }]); - }); - - it("emits new keys", async () => { - const source$ = of({ foo: true }, { foo: true, bar: true }); - const pipe$ = source$.pipe(distinctIfShallowMatch()); - - const result = trackEmissions(pipe$); - await awaitAsync(); - - expect(result).toEqual([{ foo: true }, { foo: true, bar: true }]); - }); - - it("suppresses identical values", async () => { - const source$ = of({ foo: true }, { foo: true }); - const pipe$ = source$.pipe(distinctIfShallowMatch()); - - const result = trackEmissions(pipe$); - await awaitAsync(); - - expect(result).toEqual([{ foo: true }]); - }); - - it("suppresses removed keys", async () => { - const source$ = of({ foo: true, bar: true }, { foo: true }); - const pipe$ = source$.pipe(distinctIfShallowMatch()); - - const result = trackEmissions(pipe$); - await awaitAsync(); - - expect(result).toEqual([{ foo: true, bar: true }]); - }); -}); - -describe("anyComplete", () => { - it("emits true when its input completes", () => { - const input$ = new Subject(); - - const emissions: boolean[] = []; - anyComplete(input$).subscribe((e) => emissions.push(e)); - input$.complete(); - - expect(emissions).toEqual([true]); - }); - - it("completes when its input is already complete", () => { - const input = new Subject(); - input.complete(); - - let completed = false; - anyComplete(input).subscribe({ complete: () => (completed = true) }); - - expect(completed).toBe(true); - }); - - it("completes when any input completes", () => { - const input$ = new Subject(); - const completing$ = new Subject(); - - let completed = false; - anyComplete([input$, completing$]).subscribe({ complete: () => (completed = true) }); - completing$.complete(); - - expect(completed).toBe(true); - }); - - it("ignores emissions", () => { - const input$ = new Subject(); - - const emissions: boolean[] = []; - anyComplete(input$).subscribe((e) => emissions.push(e)); - input$.next(1); - input$.next(2); - input$.complete(); - - expect(emissions).toEqual([true]); - }); - - it("forwards errors", () => { - const input$ = new Subject(); - const expected = { some: "error" }; - - let error = null; - anyComplete(input$).subscribe({ error: (e: unknown) => (error = e) }); - input$.error(expected); - - expect(error).toEqual(expected); - }); -}); - -describe("ready", () => { - it("connects when subscribed", () => { - const watch$ = new Subject(); - let connected = false; - const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); - - // precondition: ready$ should be cold - const ready$ = source$.pipe(ready(watch$)); - expect(connected).toBe(false); - - ready$.subscribe(); - - expect(connected).toBe(true); - }); - - it("suppresses source emissions until its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const results: number[] = []; - ready$.subscribe((n) => results.push(n)); - - // precondition: no emissions - source$.next(1); - expect(results).toEqual([]); - - watch$.next(); - - expect(results).toEqual([1]); - }); - - it("suppresses source emissions until all watches emit", () => { - const watchA$ = new Subject(); - const watchB$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready([watchA$, watchB$])); - const results: number[] = []; - ready$.subscribe((n) => results.push(n)); - - // preconditions: no emissions - source$.next(1); - expect(results).toEqual([]); - watchA$.next(); - expect(results).toEqual([]); - - watchB$.next(); - - expect(results).toEqual([1]); - }); - - it("emits the last source emission when its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const results: number[] = []; - ready$.subscribe((n) => results.push(n)); - - // precondition: no emissions - source$.next(1); - expect(results).toEqual([]); - - source$.next(2); - watch$.next(); - - expect(results).toEqual([2]); - }); - - it("emits all source emissions after its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const results: number[] = []; - ready$.subscribe((n) => results.push(n)); - - watch$.next(); - source$.next(1); - source$.next(2); - - expect(results).toEqual([1, 2]); - }); - - it("ignores repeated watch emissions", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const results: number[] = []; - ready$.subscribe((n) => results.push(n)); - - watch$.next(); - source$.next(1); - watch$.next(); - source$.next(2); - watch$.next(); - - expect(results).toEqual([1, 2]); - }); - - it("completes when its source completes", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - let completed = false; - ready$.subscribe({ complete: () => (completed = true) }); - - source$.complete(); - - expect(completed).toBeTruthy(); - }); - - it("errors when its source errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const expected = { some: "error" }; - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - source$.error(expected); - - expect(error).toEqual(expected); - }); - - it("errors when its watch errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const expected = { some: "error" }; - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - watch$.error(expected); - - expect(error).toEqual(expected); - }); - - it("errors when its watch completes before emitting", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - watch$.complete(); - - expect(error).toBeInstanceOf(EmptyError); - }); -}); - -describe("withLatestReady", () => { - it("connects when subscribed", () => { - const watch$ = new Subject(); - let connected = false; - const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); - - // precondition: ready$ should be cold - const ready$ = source$.pipe(withLatestReady(watch$)); - expect(connected).toBe(false); - - ready$.subscribe(); - - expect(connected).toBe(true); - }); - - it("suppresses source emissions until its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const results: [number, string][] = []; - ready$.subscribe((n) => results.push(n)); - - // precondition: no emissions - source$.next(1); - expect(results).toEqual([]); - - watch$.next("watch"); - - expect(results).toEqual([[1, "watch"]]); - }); - - it("emits the last source emission when its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const results: [number, string][] = []; - ready$.subscribe((n) => results.push(n)); - - // precondition: no emissions - source$.next(1); - expect(results).toEqual([]); - - source$.next(2); - watch$.next("watch"); - - expect(results).toEqual([[2, "watch"]]); - }); - - it("emits all source emissions after its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const results: [number, string][] = []; - ready$.subscribe((n) => results.push(n)); - - watch$.next("watch"); - source$.next(1); - source$.next(2); - - expect(results).toEqual([ - [1, "watch"], - [2, "watch"], - ]); - }); - - it("appends the latest watch emission", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const results: [number, string][] = []; - ready$.subscribe((n) => results.push(n)); - - watch$.next("ignored"); - watch$.next("watch"); - source$.next(1); - watch$.next("ignored"); - watch$.next("watch"); - source$.next(2); - - expect(results).toEqual([ - [1, "watch"], - [2, "watch"], - ]); - }); - - it("completes when its source completes", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - let completed = false; - ready$.subscribe({ complete: () => (completed = true) }); - - source$.complete(); - - expect(completed).toBeTruthy(); - }); - - it("errors when its source errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const expected = { some: "error" }; - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - source$.error(expected); - - expect(error).toEqual(expected); - }); - - it("errors when its watch errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const expected = { some: "error" }; - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - watch$.error(expected); - - expect(error).toEqual(expected); - }); - - it("errors when its watch completes before emitting", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - watch$.complete(); - - expect(error).toBeInstanceOf(EmptyError); - }); -}); - -describe("on", () => { - it("connects when subscribed", () => { - const watch$ = new Subject(); - let connected = false; - const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); - - // precondition: on$ should be cold - const on$ = source$.pipe(on(watch$)); - expect(connected).toBeFalsy(); - - on$.subscribe(); - - expect(connected).toBeTruthy(); - }); - - it("suppresses source emissions until `on` emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - - // precondition: on$ should be cold - source$.next(1); - expect(results).toEqual([]); - - watch$.next(); - - expect(results).toEqual([1]); - }); - - it("repeats source emissions when `on` emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - source$.next(1); - - watch$.next(); - watch$.next(); - - expect(results).toEqual([1, 1]); - }); - - it("updates source emissions when `on` emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - - source$.next(1); - watch$.next(); - source$.next(2); - watch$.next(); - - expect(results).toEqual([1, 2]); - }); - - it("emits a value when `on` emits before the source is ready", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - - watch$.next(); - source$.next(1); - - expect(results).toEqual([1]); - }); - - it("ignores repeated `on` emissions before the source is ready", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - - watch$.next(); - watch$.next(); - source$.next(1); - - expect(results).toEqual([1]); - }); - - it("emits only the latest source emission when `on` emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - source$.next(1); - - watch$.next(); - - source$.next(2); - source$.next(3); - watch$.next(); - - expect(results).toEqual([1, 3]); - }); - - it("completes when its source completes", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - let complete: boolean = false; - source$.pipe(on(watch$)).subscribe({ complete: () => (complete = true) }); - - source$.complete(); - - expect(complete).toBeTruthy(); - }); - - it("completes when its watch completes", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - let complete: boolean = false; - source$.pipe(on(watch$)).subscribe({ complete: () => (complete = true) }); - - watch$.complete(); - - expect(complete).toBeTruthy(); - }); - - it("errors when its source errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const expected = { some: "error" }; - let error = null; - source$.pipe(on(watch$)).subscribe({ error: (e: unknown) => (error = e) }); - - source$.error(expected); - - expect(error).toEqual(expected); - }); - - it("errors when its watch errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const expected = { some: "error" }; - let error = null; - source$.pipe(on(watch$)).subscribe({ error: (e: unknown) => (error = e) }); - - watch$.error(expected); - - expect(error).toEqual(expected); - }); -}); - -describe("pin", () => { - it("emits the first value", async () => { - const input = new Subject(); - const result: unknown[] = []; - - input.pipe(pin()).subscribe((v) => result.push(v)); - input.next(1); - - expect(result).toEqual([1]); - }); - - it("filters repeated emissions", async () => { - const input = new Subject(); - const result: unknown[] = []; - - input.pipe(pin({ distinct: (p, c) => p == c })).subscribe((v) => result.push(v)); - input.next(1); - input.next(1); - - expect(result).toEqual([1]); - }); - - it("errors if multiple emissions occur", async () => { - const input = new Subject(); - let error: any = null!; - - input.pipe(pin()).subscribe({ - error: (e: unknown) => { - error = e; - }, + expect(result).toEqual(6); }); - input.next(1); - input.next(1); - - expect(error).toBeInstanceOf(Error); - expect(error.message).toMatch(/^unknown/); }); - it("names the pinned observables if multiple emissions occur", async () => { - const input = new Subject(); - let error: any = null!; + describe("distinctIfShallowMatch", () => { + it("emits a single value", async () => { + const source$ = of({ foo: true }); + const pipe$ = source$.pipe(distinctIfShallowMatch()); - input.pipe(pin({ name: () => "example" })).subscribe({ - error: (e: unknown) => { - error = e; - }, + const result = trackEmissions(pipe$); + await awaitAsync(); + + expect(result).toEqual([{ foo: true }]); }); - input.next(1); - input.next(1); - expect(error).toBeInstanceOf(Error); - expect(error.message).toMatch(/^example/); + it("emits different values", async () => { + const source$ = of({ foo: true }, { foo: false }); + const pipe$ = source$.pipe(distinctIfShallowMatch()); + + const result = trackEmissions(pipe$); + await awaitAsync(); + + expect(result).toEqual([{ foo: true }, { foo: false }]); + }); + + it("emits new keys", async () => { + const source$ = of({ foo: true }, { foo: true, bar: true }); + const pipe$ = source$.pipe(distinctIfShallowMatch()); + + const result = trackEmissions(pipe$); + await awaitAsync(); + + expect(result).toEqual([{ foo: true }, { foo: true, bar: true }]); + }); + + it("suppresses identical values", async () => { + const source$ = of({ foo: true }, { foo: true }); + const pipe$ = source$.pipe(distinctIfShallowMatch()); + + const result = trackEmissions(pipe$); + await awaitAsync(); + + expect(result).toEqual([{ foo: true }]); + }); + + it("suppresses removed keys", async () => { + const source$ = of({ foo: true, bar: true }, { foo: true }); + const pipe$ = source$.pipe(distinctIfShallowMatch()); + + const result = trackEmissions(pipe$); + await awaitAsync(); + + expect(result).toEqual([{ foo: true, bar: true }]); + }); }); - it("errors if indistinct emissions occur", async () => { - const input = new Subject(); - let error: any = null!; + describe("anyComplete", () => { + it("emits true when its input completes", () => { + const input$ = new Subject(); - input - .pipe(pin({ distinct: (p, c) => p == c })) - .subscribe({ error: (e: unknown) => (error = e) }); - input.next(1); - input.next(2); + const emissions: boolean[] = []; + anyComplete(input$).subscribe((e) => emissions.push(e)); + input$.complete(); - expect(error).toBeInstanceOf(Error); - expect(error.message).toMatch(/^unknown/); + expect(emissions).toEqual([true]); + }); + + it("completes when its input is already complete", () => { + const input = new Subject(); + input.complete(); + + let completed = false; + anyComplete(input).subscribe({ complete: () => (completed = true) }); + + expect(completed).toBe(true); + }); + + it("completes when any input completes", () => { + const input$ = new Subject(); + const completing$ = new Subject(); + + let completed = false; + anyComplete([input$, completing$]).subscribe({ complete: () => (completed = true) }); + completing$.complete(); + + expect(completed).toBe(true); + }); + + it("ignores emissions", () => { + const input$ = new Subject(); + + const emissions: boolean[] = []; + anyComplete(input$).subscribe((e) => emissions.push(e)); + input$.next(1); + input$.next(2); + input$.complete(); + + expect(emissions).toEqual([true]); + }); + + it("forwards errors", () => { + const input$ = new Subject(); + const expected = { some: "error" }; + + let error = null; + anyComplete(input$).subscribe({ error: (e: unknown) => (error = e) }); + input$.error(expected); + + expect(error).toEqual(expected); + }); + }); + + describe("ready", () => { + it("connects when subscribed", () => { + const watch$ = new Subject(); + let connected = false; + const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); + + // precondition: ready$ should be cold + const ready$ = source$.pipe(ready(watch$)); + expect(connected).toBe(false); + + ready$.subscribe(); + + expect(connected).toBe(true); + }); + + it("suppresses source emissions until its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const results: number[] = []; + ready$.subscribe((n) => results.push(n)); + + // precondition: no emissions + source$.next(1); + expect(results).toEqual([]); + + watch$.next(); + + expect(results).toEqual([1]); + }); + + it("suppresses source emissions until all watches emit", () => { + const watchA$ = new Subject(); + const watchB$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready([watchA$, watchB$])); + const results: number[] = []; + ready$.subscribe((n) => results.push(n)); + + // preconditions: no emissions + source$.next(1); + expect(results).toEqual([]); + watchA$.next(); + expect(results).toEqual([]); + + watchB$.next(); + + expect(results).toEqual([1]); + }); + + it("emits the last source emission when its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const results: number[] = []; + ready$.subscribe((n) => results.push(n)); + + // precondition: no emissions + source$.next(1); + expect(results).toEqual([]); + + source$.next(2); + watch$.next(); + + expect(results).toEqual([2]); + }); + + it("emits all source emissions after its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const results: number[] = []; + ready$.subscribe((n) => results.push(n)); + + watch$.next(); + source$.next(1); + source$.next(2); + + expect(results).toEqual([1, 2]); + }); + + it("ignores repeated watch emissions", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const results: number[] = []; + ready$.subscribe((n) => results.push(n)); + + watch$.next(); + source$.next(1); + watch$.next(); + source$.next(2); + watch$.next(); + + expect(results).toEqual([1, 2]); + }); + + it("completes when its source completes", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + let completed = false; + ready$.subscribe({ complete: () => (completed = true) }); + + source$.complete(); + + expect(completed).toBeTruthy(); + }); + + it("errors when its source errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const expected = { some: "error" }; + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + source$.error(expected); + + expect(error).toEqual(expected); + }); + + it("errors when its watch errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const expected = { some: "error" }; + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + watch$.error(expected); + + expect(error).toEqual(expected); + }); + + it("errors when its watch completes before emitting", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + watch$.complete(); + + expect(error).toBeInstanceOf(EmptyError); + }); + }); + + describe("withLatestReady", () => { + it("connects when subscribed", () => { + const watch$ = new Subject(); + let connected = false; + const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); + + // precondition: ready$ should be cold + const ready$ = source$.pipe(withLatestReady(watch$)); + expect(connected).toBe(false); + + ready$.subscribe(); + + expect(connected).toBe(true); + }); + + it("suppresses source emissions until its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const results: [number, string][] = []; + ready$.subscribe((n) => results.push(n)); + + // precondition: no emissions + source$.next(1); + expect(results).toEqual([]); + + watch$.next("watch"); + + expect(results).toEqual([[1, "watch"]]); + }); + + it("emits the last source emission when its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const results: [number, string][] = []; + ready$.subscribe((n) => results.push(n)); + + // precondition: no emissions + source$.next(1); + expect(results).toEqual([]); + + source$.next(2); + watch$.next("watch"); + + expect(results).toEqual([[2, "watch"]]); + }); + + it("emits all source emissions after its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const results: [number, string][] = []; + ready$.subscribe((n) => results.push(n)); + + watch$.next("watch"); + source$.next(1); + source$.next(2); + + expect(results).toEqual([ + [1, "watch"], + [2, "watch"], + ]); + }); + + it("appends the latest watch emission", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const results: [number, string][] = []; + ready$.subscribe((n) => results.push(n)); + + watch$.next("ignored"); + watch$.next("watch"); + source$.next(1); + watch$.next("ignored"); + watch$.next("watch"); + source$.next(2); + + expect(results).toEqual([ + [1, "watch"], + [2, "watch"], + ]); + }); + + it("completes when its source completes", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + let completed = false; + ready$.subscribe({ complete: () => (completed = true) }); + + source$.complete(); + + expect(completed).toBeTruthy(); + }); + + it("errors when its source errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const expected = { some: "error" }; + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + source$.error(expected); + + expect(error).toEqual(expected); + }); + + it("errors when its watch errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const expected = { some: "error" }; + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + watch$.error(expected); + + expect(error).toEqual(expected); + }); + + it("errors when its watch completes before emitting", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + watch$.complete(); + + expect(error).toBeInstanceOf(EmptyError); + }); + }); + + describe("on", () => { + it("connects when subscribed", () => { + const watch$ = new Subject(); + let connected = false; + const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); + + // precondition: on$ should be cold + const on$ = source$.pipe(on(watch$)); + expect(connected).toBeFalsy(); + + on$.subscribe(); + + expect(connected).toBeTruthy(); + }); + + it("suppresses source emissions until `on` emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + + // precondition: on$ should be cold + source$.next(1); + expect(results).toEqual([]); + + watch$.next(); + + expect(results).toEqual([1]); + }); + + it("repeats source emissions when `on` emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + source$.next(1); + + watch$.next(); + watch$.next(); + + expect(results).toEqual([1, 1]); + }); + + it("updates source emissions when `on` emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + + source$.next(1); + watch$.next(); + source$.next(2); + watch$.next(); + + expect(results).toEqual([1, 2]); + }); + + it("emits a value when `on` emits before the source is ready", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + + watch$.next(); + source$.next(1); + + expect(results).toEqual([1]); + }); + + it("ignores repeated `on` emissions before the source is ready", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + + watch$.next(); + watch$.next(); + source$.next(1); + + expect(results).toEqual([1]); + }); + + it("emits only the latest source emission when `on` emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + source$.next(1); + + watch$.next(); + + source$.next(2); + source$.next(3); + watch$.next(); + + expect(results).toEqual([1, 3]); + }); + + it("completes when its source completes", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + let complete: boolean = false; + source$.pipe(on(watch$)).subscribe({ complete: () => (complete = true) }); + + source$.complete(); + + expect(complete).toBeTruthy(); + }); + + it("completes when its watch completes", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + let complete: boolean = false; + source$.pipe(on(watch$)).subscribe({ complete: () => (complete = true) }); + + watch$.complete(); + + expect(complete).toBeTruthy(); + }); + + it("errors when its source errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const expected = { some: "error" }; + let error = null; + source$.pipe(on(watch$)).subscribe({ error: (e: unknown) => (error = e) }); + + source$.error(expected); + + expect(error).toEqual(expected); + }); + + it("errors when its watch errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const expected = { some: "error" }; + let error = null; + source$.pipe(on(watch$)).subscribe({ error: (e: unknown) => (error = e) }); + + watch$.error(expected); + + expect(error).toEqual(expected); + }); + }); + + describe("pin", () => { + it("emits the first value", async () => { + const input = new Subject(); + const result: unknown[] = []; + + input.pipe(pin()).subscribe((v) => result.push(v)); + input.next(1); + + expect(result).toEqual([1]); + }); + + it("filters repeated emissions", async () => { + const input = new Subject(); + const result: unknown[] = []; + + input.pipe(pin({ distinct: (p, c) => p == c })).subscribe((v) => result.push(v)); + input.next(1); + input.next(1); + + expect(result).toEqual([1]); + }); + + it("errors if multiple emissions occur", async () => { + const input = new Subject(); + let error: any = null!; + + input.pipe(pin()).subscribe({ + error: (e: unknown) => { + error = e; + }, + }); + input.next(1); + input.next(1); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/^unknown/); + }); + + it("names the pinned observables if multiple emissions occur", async () => { + const input = new Subject(); + let error: any = null!; + + input.pipe(pin({ name: () => "example" })).subscribe({ + error: (e: unknown) => { + error = e; + }, + }); + input.next(1); + input.next(1); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/^example/); + }); + + it("errors if indistinct emissions occur", async () => { + const input = new Subject(); + let error: any = null!; + + input + .pipe(pin({ distinct: (p, c) => p == c })) + .subscribe({ error: (e: unknown) => (error = e) }); + input.next(1); + input.next(2); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/^unknown/); + }); + }); + + describe("memoizedMap", () => { + it("maps a value", () => { + const source$ = new Subject(); + const result$ = new BehaviorSubject({}); + const expectedResult = {}; + source$.pipe(memoizedMap(() => expectedResult)).subscribe(result$); + + source$.next("foo"); + + expect(result$.value).toEqual(expectedResult); + }); + + it("caches a mapped result", () => { + const source$ = new Subject(); + const result$ = new BehaviorSubject({}); + const map = jest.fn(() => ({})); + source$.pipe(memoizedMap(map)).subscribe(result$); + + source$.next("foo"); + source$.next("foo"); + + expect(map).toHaveBeenCalledTimes(1); + }); + + it("caches the last mapped result", () => { + const source$ = new Subject(); + const result$ = new BehaviorSubject({}); + const map = jest.fn(() => ({})); + source$.pipe(memoizedMap(map)).subscribe(result$); + + source$.next("foo"); + source$.next("foo"); + source$.next("bar"); + source$.next("foo"); + + expect(map).toHaveBeenCalledTimes(3); + }); + + it("caches multiple mapped results", () => { + const source$ = new Subject(); + const result$ = new BehaviorSubject({}); + const map = jest.fn(() => ({})); + source$.pipe(memoizedMap(map, { size: 2 })).subscribe(result$); + + source$.next("foo"); + source$.next("bar"); + source$.next("foo"); + source$.next("bar"); + + expect(map).toHaveBeenCalledTimes(2); + }); + + it("caches a result by key", () => { + const source$ = new Subject<{ key: string }>(); + const result$ = new BehaviorSubject({}); + const map = jest.fn(() => ({})); + source$.pipe(memoizedMap(map, { key: (s) => s.key })).subscribe(result$); + + // the messages are not equal; the keys are + source$.next({ key: "foo" }); + source$.next({ key: "foo" }); + source$.next({ key: "bar" }); + source$.next({ key: "bar" }); + + expect(map).toHaveBeenCalledTimes(2); + }); + }); + + it("errors", () => { + const source$ = new Subject(); + let error: unknown = null; + source$.pipe(memoizedMap(() => {})).subscribe({ error: (e: unknown) => (error = e) }); + const expectedError = {}; + + source$.error(expectedError); + + expect(error).toEqual(expectedError); + }); + + it("completes", () => { + const source$ = new Subject(); + let completed = false; + source$.pipe(memoizedMap(() => {})).subscribe({ complete: () => (completed = true) }); + + source$.complete(); + + expect(completed).toEqual(true); }); }); diff --git a/libs/common/src/tools/rx.ts b/libs/common/src/tools/rx.ts index ea397135581..4c9e547d151 100644 --- a/libs/common/src/tools/rx.ts +++ b/libs/common/src/tools/rx.ts @@ -20,8 +20,13 @@ import { startWith, pairwise, MonoTypeOperatorFunction, + Cons, + scan, + filter, } from "rxjs"; +import { ObservableTuple } from "./rx.rxjs"; + /** Returns its input. */ function identity(value: any): any { return value; @@ -164,26 +169,30 @@ export function ready(watch$: Observable | Observable[]) { ); } -export function withLatestReady( - watch$: Observable, -): OperatorFunction { +export function withLatestReady( + ...watches$: [...ObservableTuple] +): OperatorFunction> { return connect((source$) => { // these subscriptions are safe because `source$` connects only after there // is an external subscriber. const source = new ReplaySubject(1); source$.subscribe(source); - const watch = new ReplaySubject(1); - watch$.subscribe(watch); + + const watches = watches$.map((w) => { + const watch$ = new ReplaySubject(1); + w.subscribe(watch$); + return watch$; + }) as [...ObservableTuple]; // `concat` is subscribed immediately after it's returned, at which point - // `zip` blocks until all items in `watching$` are ready. If that occurs + // `zip` blocks until all items in `watches` are ready. If that occurs // after `source$` is hot, then the replay subject sends the last-captured - // emission through immediately. Otherwise, `ready` waits for the next - // emission - return concat(zip(watch).pipe(first(), ignoreElements()), source).pipe( - withLatestFrom(watch), + // emission through immediately. Otherwise, `withLatestFrom` waits for the + // next emission + return concat(zip(watches).pipe(first(), ignoreElements()), source).pipe( + withLatestFrom(...watches), takeUntil(anyComplete(source)), - ); + ) as Observable>; }); } @@ -238,3 +247,54 @@ export function pin(options?: { }), ); } + +/** maps a value to a result and keeps a cache of the mapping + * @param mapResult - maps the stream to a result; this function must return + * a value. It must not return null or undefined. + * @param options.size - the number of entries in the cache + * @param options.key - maps the source to a cache key + * @remarks this method is useful for optimization of expensive + * `mapResult` calls. It's also useful when an interned reference type + * is needed. + */ +export function memoizedMap>( + mapResult: (source: Source) => Result, + options?: { size?: number; key?: (source: Source) => unknown }, +): OperatorFunction { + return pipe( + // scan's accumulator contains the cache + scan( + ([cache], source) => { + const key: unknown = options?.key?.(source) ?? source; + + // cache hit? + let result = cache?.get(key); + if (result) { + return [cache, result] as const; + } + + // cache miss + result = mapResult(source); + cache?.set(key, result); + + // trim cache + const overage = cache.size - (options?.size ?? 1); + if (overage > 0) { + Array.from(cache?.keys() ?? []) + .slice(0, overage) + .forEach((k) => cache?.delete(k)); + } + + return [cache, result] as const; + }, + // FIXME: upgrade to a least-recently-used cache + [new Map(), null] as [Map, Source | null], + ), + + // encapsulate cache + map(([, result]) => result), + + // preserve `NonNullable` constraint on `Result` + filter((result): result is Result => !!result), + ); +} diff --git a/libs/common/src/tools/state/user-state-subject-dependency-provider.ts b/libs/common/src/tools/state/user-state-subject-dependency-provider.ts index 2763aac4830..89b6d7e7270 100644 --- a/libs/common/src/tools/state/user-state-subject-dependency-provider.ts +++ b/libs/common/src/tools/state/user-state-subject-dependency-provider.ts @@ -15,4 +15,9 @@ export abstract class UserStateSubjectDependencyProvider { // FIXME: remove `log` and inject the system provider into the USS instead /** Provides semantic logging */ abstract log: (_context: Jsonify) => SemanticLogger; + + /** Get the system time as a number of seconds since the unix epoch + * @remarks this can be turned into a date using `new Date(provider.now())` + */ + abstract now: () => number; } diff --git a/libs/common/src/tools/state/user-state-subject.spec.ts b/libs/common/src/tools/state/user-state-subject.spec.ts index 20acff17877..a6d452d37fd 100644 --- a/libs/common/src/tools/state/user-state-subject.spec.ts +++ b/libs/common/src/tools/state/user-state-subject.spec.ts @@ -90,6 +90,7 @@ const SomeProvider = { } as LegacyEncryptorProvider, state: SomeStateProvider, log: disabledSemanticLoggerProvider, + now: () => 100, }; function fooMaxLength(maxLength: number): StateConstraints { diff --git a/libs/common/src/tools/state/user-state-subject.ts b/libs/common/src/tools/state/user-state-subject.ts index dd88ec2fb20..118b0069c84 100644 --- a/libs/common/src/tools/state/user-state-subject.ts +++ b/libs/common/src/tools/state/user-state-subject.ts @@ -477,7 +477,12 @@ export class UserStateSubject< * @returns the subscription */ subscribe(observer?: Partial> | ((value: State) => void) | null): Subscription { - return this.output.pipe(map((wc) => wc.state)).subscribe(observer); + return this.output + .pipe( + map((wc) => wc.state), + distinctUntilChanged(), + ) + .subscribe(observer); } // using subjects to ensure the right semantics are followed; diff --git a/libs/common/src/tools/types.ts b/libs/common/src/tools/types.ts index 83f451351c2..6123b1f1dea 100644 --- a/libs/common/src/tools/types.ts +++ b/libs/common/src/tools/types.ts @@ -2,6 +2,11 @@ import { Simplify } from "type-fest"; import { IntegrationId } from "./integration"; +/** When this is a string, it contains the i18n key. When it is an object, the `literal` member + * contains text that should not be translated. + */ +export type I18nKeyOrLiteral = string | { literal: string }; + /** Constraints that are shared by all primitive field types */ type PrimitiveConstraint = { /** `true` indicates the field is required; otherwise the field is optional */ diff --git a/libs/common/src/tools/util.ts b/libs/common/src/tools/util.ts index 9a3a14c1c83..0fcb5a972af 100644 --- a/libs/common/src/tools/util.ts +++ b/libs/common/src/tools/util.ts @@ -1,3 +1,5 @@ +import { I18nKeyOrLiteral } from "./types"; + /** Recursively freeze an object's own keys * @param value the value to freeze * @returns `value` @@ -10,10 +12,22 @@ export function deepFreeze(value: T): Readonly { for (const key of keys) { const own = value[key]; - if ((own && typeof own === "object") || typeof own === "function") { + if (own && typeof own === "object") { deepFreeze(own); } } return Object.freeze(value); } + +/** Type guard that returns `true` when the value is an i18n key. */ +export function isI18nKey(value: I18nKeyOrLiteral): value is string { + return typeof value === "string"; +} + +/** Type guard that returns `true` when the value requires no translation. + * @remarks the literal value can be accessed using the `.literal` property. + */ +export function isLiteral(value: I18nKeyOrLiteral): value is { literal: string } { + return typeof value === "object" && "literal" in value; +} diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts index 0512b56e20d..956cc611c2e 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts @@ -57,7 +57,7 @@ import { ToastService, } from "@bitwarden/components"; import { GeneratorServicesModule } from "@bitwarden/generator-components"; -import { CredentialGeneratorService, GenerateRequest, Generators } from "@bitwarden/generator-core"; +import { CredentialGeneratorService, GenerateRequest, Type } from "@bitwarden/generator-core"; import { ExportedVault, VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; import { EncryptedExportType } from "../enums/encrypted-export-type.enum"; @@ -248,7 +248,7 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { }), ); this.generatorService - .generate$(Generators.password, { on$: this.onGenerate$, account$ }) + .generate$({ on$: this.onGenerate$, account$ }) .pipe(takeUntil(this.destroy$)) .subscribe((generated) => { this.exportForm.patchValue({ @@ -379,7 +379,7 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { } generatePassword = async () => { - this.onGenerate$.next({ source: "export" }); + this.onGenerate$.next({ source: "export", type: Type.password }); }; submit = async () => { diff --git a/libs/tools/generator/components/src/catchall-settings.component.ts b/libs/tools/generator/components/src/catchall-settings.component.ts index 3bf586c5a29..a836b26f98b 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.ts +++ b/libs/tools/generator/components/src/catchall-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, EventEmitter, @@ -17,7 +15,7 @@ import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { CatchallGenerationOptions, CredentialGeneratorService, - Generators, + BuiltIn, } from "@bitwarden/generator-core"; /** Options group for catchall emails */ @@ -28,7 +26,6 @@ import { }) export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { /** Instantiates the component - * @param accountService queries user availability * @param generatorService settings and policy logic * @param formBuilder reactive form controls */ @@ -37,24 +34,26 @@ export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { private generatorService: CredentialGeneratorService, ) {} - /** Binds the component to a specific user's settings. + /** Binds the component to a specific user's settings.\ + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account!: Account; private account$ = new ReplaySubject(1); /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, - * use `CredentialGeneratorService.settings$(...)` instead. + * use `CredentialGeneratorService.settings(...)` instead. */ @Output() readonly onUpdated = new EventEmitter(); /** The template's control bindings */ protected settings = this.formBuilder.group({ - catchallDomain: [Generators.catchall.settings.initial.catchallDomain], + catchallDomain: [""], }); async ngOnChanges(changes: SimpleChanges) { @@ -64,7 +63,7 @@ export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { } async ngOnInit() { - const settings = await this.generatorService.settings(Generators.catchall, { + const settings = await this.generatorService.settings(BuiltIn.catchall, { account$: this.account$, }); @@ -79,7 +78,7 @@ export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { this.saveSettings .pipe( withLatestFrom(this.settings.valueChanges), - map(([, settings]) => settings), + map(([, settings]) => settings as CatchallGenerationOptions), takeUntil(this.destroyed$), ) .subscribe(settings); diff --git a/libs/tools/generator/components/src/credential-generator-history.component.ts b/libs/tools/generator/components/src/credential-generator-history.component.ts index e7dadb3da71..76dfbaea867 100644 --- a/libs/tools/generator/components/src/credential-generator-history.component.ts +++ b/libs/tools/generator/components/src/credential-generator-history.component.ts @@ -6,6 +6,7 @@ import { BehaviorSubject, ReplaySubject, Subject, map, switchMap, takeUntil, tap import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SemanticLogger, @@ -19,10 +20,11 @@ import { ItemModule, NoItemsModule, } from "@bitwarden/components"; -import { CredentialGeneratorService } from "@bitwarden/generator-core"; +import { AlgorithmsByType, CredentialGeneratorService } from "@bitwarden/generator-core"; import { GeneratedCredential, GeneratorHistoryService } from "@bitwarden/generator-history"; import { GeneratorModule } from "./generator.module"; +import { translate } from "./util"; @Component({ standalone: true, @@ -45,6 +47,7 @@ export class CredentialGeneratorHistoryComponent implements OnChanges, OnInit, O constructor( private generatorService: CredentialGeneratorService, private history: GeneratorHistoryService, + private i18nService: I18nService, private logService: LogService, ) {} @@ -94,13 +97,19 @@ export class CredentialGeneratorHistoryComponent implements OnChanges, OnInit, O } protected getCopyText(credential: GeneratedCredential) { - const info = this.generatorService.algorithm(credential.category); - return info.copy; + // there isn't a way way to look up category metadata so + // bodge it by looking up algorithm metadata + const [id] = AlgorithmsByType[credential.category]; + const info = this.generatorService.algorithm(id); + return translate(info.i18nKeys.copyCredential, this.i18nService); } protected getGeneratedValueText(credential: GeneratedCredential) { - const info = this.generatorService.algorithm(credential.category); - return info.credentialType; + // there isn't a way way to look up category metadata so + // bodge it by looking up algorithm metadata + const [id] = AlgorithmsByType[credential.category]; + const info = this.generatorService.algorithm(id); + return translate(info.i18nKeys.credentialType, this.i18nService); } ngOnDestroy() { diff --git a/libs/tools/generator/components/src/credential-generator.component.html b/libs/tools/generator/components/src/credential-generator.component.html index 24146968456..559554b7ddb 100644 --- a/libs/tools/generator/components/src/credential-generator.component.html +++ b/libs/tools/generator/components/src/credential-generator.component.html @@ -42,13 +42,13 @@ @@ -84,7 +84,7 @@ @@ -94,12 +94,12 @@ [forwarder]="forwarderId$ | async" /> diff --git a/libs/tools/generator/components/src/credential-generator.component.ts b/libs/tools/generator/components/src/credential-generator.component.ts index 0b48b4cf0f7..78b803392df 100644 --- a/libs/tools/generator/components/src/credential-generator.component.ts +++ b/libs/tools/generator/components/src/credential-generator.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { LiveAnnouncer } from "@angular/cdk/a11y"; import { Component, @@ -24,15 +22,15 @@ import { map, ReplaySubject, Subject, - switchMap, takeUntil, + tap, withLatestFrom, } from "rxjs"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { IntegrationId } from "@bitwarden/common/tools/integration"; +import { VendorId } from "@bitwarden/common/tools/extension"; import { SemanticLogger, disabledSemanticLoggerProvider, @@ -41,23 +39,25 @@ import { import { UserId } from "@bitwarden/common/types/guid"; import { ToastService, Option } from "@bitwarden/components"; import { - AlgorithmInfo, - CredentialAlgorithm, - CredentialCategory, + CredentialType, CredentialGeneratorService, GenerateRequest, GeneratedCredential, - Generators, - getForwarderConfiguration, - isEmailAlgorithm, - isForwarderIntegration, - isPasswordAlgorithm, + isForwarderExtensionId, isSameAlgorithm, + isEmailAlgorithm, isUsernameAlgorithm, - toCredentialGeneratorConfiguration, + isPasswordAlgorithm, + CredentialAlgorithm, + AlgorithmMetadata, + Algorithm, + AlgorithmsByType, + Type, } from "@bitwarden/generator-core"; import { GeneratorHistoryService } from "@bitwarden/generator-history"; +import { translate } from "./util"; + // constants used to identify navigation selections that are not // generator algorithms const IDENTIFIER = "identifier"; @@ -84,11 +84,14 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro private ariaLive: LiveAnnouncer, ) {} + /** exports algorithm symbols to the template */ + protected readonly Algorithm = Algorithm; + /** Binds the component to a specific user's settings. When this input is not provided, * the form binds to the active user */ @Input() - account: Account | null; + account: Account | null = null; /** Send structured debug logs from the credential generator component * to the debugger console. @@ -127,7 +130,7 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro @Output() readonly onGenerated = new EventEmitter(); - protected root$ = new BehaviorSubject<{ nav: string }>({ + protected root$ = new BehaviorSubject<{ nav: string | null }>({ nav: null, }); @@ -141,11 +144,11 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro } protected username = this.formBuilder.group({ - nav: [null as string], + nav: [null as string | null], }); protected forwarder = this.formBuilder.group({ - nav: [null as string], + nav: [null as string | null], }); async ngOnInit() { @@ -154,33 +157,52 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro }); if (!this.account) { - this.account = await firstValueFrom(this.accountService.activeAccount$); - this.log.info( - { userId: this.account.id }, - "account not specified; using active account settings", - ); - this.account$.next(this.account); + const account = await firstValueFrom(this.accountService.activeAccount$); + if (!account) { + this.log.panic("active account cannot be `null`."); + } + + this.log.info({ userId: account.id }, "account not specified; using active account settings"); + this.account$.next(account); } - this.generatorService - .algorithms$(["email", "username"], { account$: this.account$ }) + combineLatest([ + this.generatorService.algorithms$("email", { account$: this.account$ }), + this.generatorService.algorithms$("username", { account$: this.account$ }), + ]) .pipe( + map((algorithms) => algorithms.flat()), map((algorithms) => { - const usernames = algorithms.filter((a) => !isForwarderIntegration(a.id)); + // construct options for username and email algorithms; replace forwarder + // entry with a virtual entry for drill-down + const usernames = algorithms.filter((a) => !isForwarderExtensionId(a.id)); + usernames.sort((a, b) => a.weight - b.weight); const usernameOptions = this.toOptions(usernames); - usernameOptions.push({ value: FORWARDER, label: this.i18nService.t("forwardedEmail") }); + usernameOptions.splice(-1, 0, { + value: FORWARDER, + label: this.i18nService.t("forwardedEmail"), + }); - const forwarders = algorithms.filter((a) => isForwarderIntegration(a.id)); + // construct options for forwarder algorithms; they get their own selection box + const forwarders = algorithms.filter((a) => isForwarderExtensionId(a.id)); + forwarders.sort((a, b) => a.weight - b.weight); const forwarderOptions = this.toOptions(forwarders); forwarderOptions.unshift({ value: NONE_SELECTED, label: this.i18nService.t("select") }); return [usernameOptions, forwarderOptions] as const; }), + tap((algorithms) => + this.log.debug({ algorithms: algorithms as object }, "algorithms loaded"), + ), takeUntil(this.destroyed), ) .subscribe(([usernames, forwarders]) => { - this.usernameOptions$.next(usernames); - this.forwarderOptions$.next(forwarders); + // update subjects within the angular zone so that the + // template bindings refresh immediately + this.zone.run(() => { + this.usernameOptions$.next(usernames); + this.forwarderOptions$.next(forwarders); + }); }); this.generatorService @@ -195,9 +217,15 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro ) .subscribe(this.rootOptions$); - this.algorithm$ + this.maybeAlgorithm$ .pipe( - map((a) => a?.description), + map((a) => { + if (a?.i18nKeys?.description) { + return translate(a.i18nKeys.description, this.i18nService); + } else { + return ""; + } + }), takeUntil(this.destroyed), ) .subscribe((hint) => { @@ -208,9 +236,9 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro }); }); - this.algorithm$ + this.maybeAlgorithm$ .pipe( - map((a) => a?.category), + map((a) => a?.type), distinctUntilChanged(), takeUntil(this.destroyed), ) @@ -223,10 +251,12 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro }); // wire up the generator - this.algorithm$ + this.generatorService + .generate$({ + on$: this.generate$, + account$: this.account$, + }) .pipe( - filter((algorithm) => !!algorithm), - switchMap((algorithm) => this.typeToGenerator$(algorithm.id)), catchError((error: unknown, generator) => { if (typeof error === "string") { this.toastService.showToast({ @@ -241,11 +271,14 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro // continue with origin stream return generator; }), - withLatestFrom(this.account$, this.algorithm$), + withLatestFrom(this.account$, this.maybeAlgorithm$), takeUntil(this.destroyed), ) .subscribe(([generated, account, algorithm]) => { - this.log.debug({ source: generated.source }, "credential generated"); + this.log.debug( + { source: generated.source ?? null, algorithm: algorithm?.id ?? null }, + "credential generated", + ); this.generatorHistoryService .track(account.id, generated.credential, generated.category, generated.generationDate) @@ -256,8 +289,8 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro // update subjects within the angular zone so that the // template bindings refresh immediately this.zone.run(() => { - if (generated.source === this.USER_REQUEST) { - this.announce(algorithm.onGeneratedMessage); + if (algorithm && generated.source === this.USER_REQUEST) { + this.announce(translate(algorithm.i18nKeys.credentialGenerated, this.i18nService)); } this.generatedCredential$.next(generated); @@ -274,36 +307,46 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro this.root$ .pipe( - map( - (root): CascadeValue => - root.nav === IDENTIFIER - ? { nav: root.nav } - : { nav: root.nav, algorithm: JSON.parse(root.nav) }, - ), + map((root): CascadeValue => { + if (root.nav === IDENTIFIER) { + return { nav: root.nav }; + } else if (root.nav) { + return { nav: root.nav, algorithm: JSON.parse(root.nav) }; + } else { + return { nav: IDENTIFIER }; + } + }), takeUntil(this.destroyed), ) .subscribe(activeRoot$); this.username.valueChanges .pipe( - map( - (username): CascadeValue => - username.nav === FORWARDER - ? { nav: username.nav } - : { nav: username.nav, algorithm: JSON.parse(username.nav) }, - ), + map((username): CascadeValue => { + if (username.nav === FORWARDER) { + return { nav: username.nav }; + } else if (username.nav) { + return { nav: username.nav, algorithm: JSON.parse(username.nav) }; + } else { + const [algorithm] = AlgorithmsByType[Type.username]; + return { nav: JSON.stringify(algorithm), algorithm }; + } + }), takeUntil(this.destroyed), ) .subscribe(activeIdentifier$); this.forwarder.valueChanges .pipe( - map( - (forwarder): CascadeValue => - forwarder.nav === NONE_SELECTED - ? { nav: forwarder.nav } - : { nav: forwarder.nav, algorithm: JSON.parse(forwarder.nav) }, - ), + map((forwarder): CascadeValue => { + if (forwarder.nav === NONE_SELECTED) { + return { nav: forwarder.nav }; + } else if (forwarder.nav) { + return { nav: forwarder.nav, algorithm: JSON.parse(forwarder.nav) }; + } else { + return { nav: NONE_SELECTED }; + } + }), takeUntil(this.destroyed), ) .subscribe(activeForwarder$); @@ -314,7 +357,7 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro map(([root, username, forwarder]) => { const showForwarder = !root.algorithm && !username.algorithm; const forwarderId = - showForwarder && isForwarderIntegration(forwarder.algorithm) + showForwarder && forwarder.algorithm && isForwarderExtensionId(forwarder.algorithm) ? forwarder.algorithm.forwarder : null; return [showForwarder, forwarderId] as const; @@ -344,47 +387,51 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro return null; } }), - distinctUntilChanged((prev, next) => isSameAlgorithm(prev?.id, next?.id)), + distinctUntilChanged((prev, next) => { + if (prev === null || next === null) { + return false; + } else { + return isSameAlgorithm(prev.id, next.id); + } + }), takeUntil(this.destroyed), ) .subscribe((algorithm) => { - this.log.debug(algorithm, "algorithm selected"); + this.log.debug({ algorithm: algorithm?.id ?? null }, "algorithm selected"); // update subjects within the angular zone so that the // template bindings refresh immediately this.zone.run(() => { - this.algorithm$.next(algorithm); + this.maybeAlgorithm$.next(algorithm); }); }); // assume the last-selected generator algorithm is the user's preferred one const preferences = await this.generatorService.preferences({ account$: this.account$ }); this.algorithm$ - .pipe( - filter((algorithm) => !!algorithm), - withLatestFrom(preferences), - takeUntil(this.destroyed), - ) + .pipe(withLatestFrom(preferences), takeUntil(this.destroyed)) .subscribe(([algorithm, preference]) => { - function setPreference(category: CredentialCategory, log: SemanticLogger) { - const p = preference[category]; + function setPreference(type: CredentialType) { + const p = preference[type]; p.algorithm = algorithm.id; p.updated = new Date(); - - log.info({ algorithm, category }, "algorithm preferences updated"); } // `is*Algorithm` decides `algorithm`'s type, which flows into `setPreference` if (isEmailAlgorithm(algorithm.id)) { - setPreference("email", this.log); + setPreference("email"); } else if (isUsernameAlgorithm(algorithm.id)) { - setPreference("username", this.log); + setPreference("username"); } else if (isPasswordAlgorithm(algorithm.id)) { - setPreference("password", this.log); + setPreference("password"); } else { return; } + this.log.info( + { algorithm: algorithm.id, type: algorithm.type }, + "algorithm preferences updated", + ); preferences.next(preference); }); @@ -392,10 +439,12 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro preferences .pipe( map(({ email, username, password }) => { - const forwarderPref = isForwarderIntegration(email.algorithm) ? email : null; const usernamePref = email.updated > username.updated ? email : username; + const forwarderPref = isForwarderExtensionId(usernamePref.algorithm) + ? usernamePref + : null; - // inject drilldown flags + // inject drill-down flags const forwarderNav = !forwarderPref ? NONE_SELECTED : JSON.stringify(forwarderPref.algorithm); @@ -411,14 +460,14 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro selection: { nav: rootNav }, active: { nav: rootNav, - algorithm: rootNav === IDENTIFIER ? null : password.algorithm, + algorithm: rootNav === IDENTIFIER ? undefined : password.algorithm, } as CascadeValue, }, username: { selection: { nav: userNav }, active: { nav: userNav, - algorithm: forwarderPref ? null : usernamePref.algorithm, + algorithm: forwarderPref ? undefined : usernamePref.algorithm, }, }, forwarder: { @@ -435,6 +484,15 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro takeUntil(this.destroyed), ) .subscribe(({ root, username, forwarder }) => { + this.log.debug( + { + root: root.selection, + username: username.selection, + forwarder: forwarder.selection, + }, + "navigation updated", + ); + // update navigation; break subscription loop this.onRootChanged(root.selection); this.username.setValue(username.selection, { emitEvent: false }); @@ -448,16 +506,16 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro // automatically regenerate when the algorithm switches if the algorithm // allows it; otherwise set a placeholder - this.algorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { + this.maybeAlgorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { this.zone.run(() => { - if (!a || a.onlyOnRequest) { - this.log.debug("autogeneration disabled; clearing generated credential"); - this.generatedCredential$.next(null); - } else { + if (a?.capabilities?.autogenerate) { this.log.debug("autogeneration enabled"); this.generate("autogenerate").catch((e: unknown) => { this.log.error(e as object, "a failure occurred during autogeneration"); }); + } else { + this.log.debug("autogeneration disabled; clearing generated credential"); + this.generatedCredential$.next(undefined); } }); }); @@ -469,41 +527,6 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro this.ariaLive.announce(message).catch((e) => this.logService.error(e)); } - private typeToGenerator$(algorithm: CredentialAlgorithm) { - const dependencies = { - on$: this.generate$, - account$: this.account$, - }; - - this.log.debug({ algorithm }, "constructing generation stream"); - - switch (algorithm) { - case "catchall": - return this.generatorService.generate$(Generators.catchall, dependencies); - - case "subaddress": - return this.generatorService.generate$(Generators.subaddress, dependencies); - - case "username": - return this.generatorService.generate$(Generators.username, dependencies); - - case "password": - return this.generatorService.generate$(Generators.password, dependencies); - - case "passphrase": - return this.generatorService.generate$(Generators.passphrase, dependencies); - } - - if (isForwarderIntegration(algorithm)) { - const forwarder = getForwarderConfiguration(algorithm.forwarder); - const configuration = toCredentialGeneratorConfiguration(forwarder); - const generator = this.generatorService.generate$(configuration, dependencies); - return generator; - } - - this.log.panic({ algorithm }, `Invalid generator type: "${algorithm}"`); - } - /** Lists the top-level credential types supported by the component. * @remarks This is string-typed because angular doesn't support * structural equality for objects, which prevents `CredentialAlgorithm` @@ -519,15 +542,20 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro protected forwarderOptions$ = new BehaviorSubject[]>([]); /** Tracks the currently selected forwarder. */ - protected forwarderId$ = new BehaviorSubject(null); + protected forwarderId$ = new BehaviorSubject(null); /** Tracks forwarder control visibility */ protected showForwarder$ = new BehaviorSubject(false); /** tracks the currently selected credential type */ - protected algorithm$ = new ReplaySubject(1); + protected maybeAlgorithm$ = new ReplaySubject(1); - protected showAlgorithm$ = this.algorithm$.pipe( + /** tracks the last valid algorithm selection */ + protected algorithm$ = this.maybeAlgorithm$.pipe( + filter((algorithm): algorithm is AlgorithmMetadata => !!algorithm), + ); + + protected showAlgorithm$ = this.maybeAlgorithm$.pipe( combineLatestWith(this.showForwarder$), map(([algorithm, showForwarder]) => (showForwarder ? null : algorithm)), ); @@ -536,33 +564,32 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro * Emits the copy button aria-label respective of the selected credential type */ protected credentialTypeCopyLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ copy }) => copy), + map(({ i18nKeys: { copyCredential } }) => translate(copyCredential, this.i18nService)), ); /** * Emits the generate button aria-label respective of the selected credential type */ protected credentialTypeGenerateLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ generate }) => generate), + map(({ i18nKeys: { generateCredential } }) => translate(generateCredential, this.i18nService)), ); /** * Emits the copy credential toast respective of the selected credential type */ protected credentialTypeLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ credentialType }) => credentialType), + map(({ i18nKeys: { credentialType } }) => translate(credentialType, this.i18nService)), ); /** Emits hint key for the currently selected credential type */ - protected credentialTypeHint$ = new ReplaySubject(1); + protected credentialTypeHint$ = new ReplaySubject(1); /** tracks the currently selected credential category */ - protected category$ = new ReplaySubject(1); + protected category$ = new ReplaySubject(1); - private readonly generatedCredential$ = new BehaviorSubject(null); + private readonly generatedCredential$ = new BehaviorSubject( + undefined, + ); /** Emits the last generated value. */ protected readonly value$ = this.generatedCredential$.pipe( @@ -580,15 +607,20 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro * origin in the debugger. */ protected async generate(source: string) { - const request = { source, website: this.website }; + const algorithm = await firstValueFrom(this.algorithm$); + const request: GenerateRequest = { source, algorithm: algorithm.id }; + if (this.website) { + request.website = this.website; + } + this.log.debug(request, "generation requested"); this.generate$.next(request); } - private toOptions(algorithms: AlgorithmInfo[]) { + private toOptions(algorithms: AlgorithmMetadata[]) { const options: Option[] = algorithms.map((algorithm) => ({ value: JSON.stringify(algorithm.id), - label: algorithm.name, + label: translate(algorithm.i18nKeys.name, this.i18nService), })); return options; diff --git a/libs/tools/generator/components/src/forwarder-settings.component.ts b/libs/tools/generator/components/src/forwarder-settings.component.ts index 689cc7e258c..c961cd5bb7a 100644 --- a/libs/tools/generator/components/src/forwarder-settings.component.ts +++ b/libs/tools/generator/components/src/forwarder-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, EventEmitter, @@ -14,13 +12,11 @@ import { FormBuilder } from "@angular/forms"; import { map, ReplaySubject, skip, Subject, switchAll, takeUntil, withLatestFrom } from "rxjs"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; -import { IntegrationId } from "@bitwarden/common/tools/integration"; +import { VendorId } from "@bitwarden/common/tools/extension"; import { - CredentialGeneratorConfiguration, CredentialGeneratorService, - getForwarderConfiguration, - NoPolicy, - toCredentialGeneratorConfiguration, + ForwarderOptions, + GeneratorMetadata, } from "@bitwarden/generator-core"; const Controls = Object.freeze({ @@ -37,7 +33,6 @@ const Controls = Object.freeze({ }) export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component - * @param accountService queries user availability * @param generatorService settings and policy logic * @param formBuilder reactive form controls */ @@ -47,14 +42,16 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy ) {} /** Binds the component to a specific user's settings. + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account: Account = null!; protected account$ = new ReplaySubject(1); @Input({ required: true }) - forwarder: IntegrationId; + forwarder: VendorId = null!; /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like @@ -71,24 +68,19 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy [Controls.baseUrl]: [""], }); - private forwarderId$ = new ReplaySubject(1); + private vendor = new ReplaySubject(1); async ngOnInit() { - const forwarder$ = new ReplaySubject>(1); - this.forwarderId$ + const forwarder$ = new ReplaySubject>(1); + this.vendor .pipe( - map((id) => getForwarderConfiguration(id)), - // type erasure necessary because the configuration properties are - // determined dynamically at runtime - // FIXME: this can be eliminated by unifying the forwarder settings types; - // see `ForwarderConfiguration<...>` for details. - map((forwarder) => toCredentialGeneratorConfiguration(forwarder)), + map((vendor) => this.generatorService.forwarder(vendor)), takeUntil(this.destroyed$), ) .subscribe((forwarder) => { - this.displayDomain = forwarder.request.includes("domain"); - this.displayToken = forwarder.request.includes("token"); - this.displayBaseUrl = forwarder.request.includes("baseUrl"); + this.displayDomain = forwarder.capabilities.fields.includes("domain"); + this.displayToken = forwarder.capabilities.fields.includes("token"); + this.displayBaseUrl = forwarder.capabilities.fields.includes("baseUrl"); forwarder$.next(forwarder); }); @@ -107,10 +99,10 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy forwarder$.pipe(takeUntil(this.destroyed$)).subscribe((forwarder) => { for (const name in Controls) { const control = this.settings.get(name); - if (forwarder.request.includes(name as any)) { - control.enable({ emitEvent: false }); + if (forwarder.capabilities.fields.includes(name)) { + control?.enable({ emitEvent: false }); } else { - control.disable({ emitEvent: false }); + control?.disable({ emitEvent: false }); } } }); @@ -128,7 +120,7 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy this.saveSettings .pipe(withLatestFrom(this.settings.valueChanges, settings$), takeUntil(this.destroyed$)) .subscribe(([, value, settings]) => { - settings.next(value); + settings.next(value as ForwarderOptions); }); } @@ -140,7 +132,7 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy async ngOnChanges(changes: SimpleChanges) { this.refresh$.complete(); if ("forwarder" in changes) { - this.forwarderId$.next(this.forwarder); + this.vendor.next(this.forwarder); } if ("account" in changes) { @@ -148,9 +140,9 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy } } - protected displayDomain: boolean; - protected displayToken: boolean; - protected displayBaseUrl: boolean; + protected displayDomain: boolean = false; + protected displayToken: boolean = false; + protected displayBaseUrl: boolean = false; private readonly refresh$ = new Subject(); diff --git a/libs/tools/generator/components/src/generator-services.module.ts b/libs/tools/generator/components/src/generator-services.module.ts index 214abbd0ac2..3a7b771a25d 100644 --- a/libs/tools/generator/components/src/generator-services.module.ts +++ b/libs/tools/generator/components/src/generator-services.module.ts @@ -7,19 +7,40 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.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"; import { StateProvider } from "@bitwarden/common/platform/state"; import { KeyServiceLegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/key-service-legacy-encryptor-provider"; import { LegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/legacy-encryptor-provider"; -import { disabledSemanticLoggerProvider } from "@bitwarden/common/tools/log"; +import { Site } from "@bitwarden/common/tools/extension"; +import { ExtensionRegistry } from "@bitwarden/common/tools/extension/extension-registry.abstraction"; +import { ExtensionService } from "@bitwarden/common/tools/extension/extension.service"; +import { DefaultFields, DefaultSites, Extension } from "@bitwarden/common/tools/extension/metadata"; +import { RuntimeExtensionRegistry } from "@bitwarden/common/tools/extension/runtime-extension-registry"; +import { VendorExtensions, Vendors } from "@bitwarden/common/tools/extension/vendor"; +import { RestClient } from "@bitwarden/common/tools/integration/rpc"; +import { + LogProvider, + disabledSemanticLoggerProvider, + enableLogForTypes, +} from "@bitwarden/common/tools/log"; +import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider"; import { + BuiltIn, createRandomizer, CredentialGeneratorService, Randomizer, + providers, + DefaultCredentialGeneratorService, } from "@bitwarden/generator-core"; import { KeyService } from "@bitwarden/key-management"; export const RANDOMIZER = new SafeInjectionToken("Randomizer"); +const GENERATOR_SERVICE_PROVIDER = new SafeInjectionToken( + "CredentialGeneratorProviders", +); +const SYSTEM_SERVICE_PROVIDER = new SafeInjectionToken("SystemServices"); /** Shared module containing generator component dependencies */ @NgModule({ @@ -35,6 +56,116 @@ export const RANDOMIZER = new SafeInjectionToken("Randomizer"); useClass: KeyServiceLegacyEncryptorProvider, deps: [EncryptService, KeyService], }), + safeProvider({ + provide: ExtensionRegistry, + useFactory: () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + registry.registerSite(Extension[Site.forwarder]); + for (const vendor of Vendors) { + registry.registerVendor(vendor); + } + for (const extension of VendorExtensions) { + registry.registerExtension(extension); + } + registry.setPermission({ all: true }, "default"); + + return registry; + }, + deps: [], + }), + safeProvider({ + provide: SYSTEM_SERVICE_PROVIDER, + useFactory: ( + encryptor: LegacyEncryptorProvider, + state: StateProvider, + policy: PolicyService, + registry: ExtensionRegistry, + logger: LogService, + environment: PlatformUtilsService, + ) => { + let log: LogProvider; + if (environment.isDev()) { + log = enableLogForTypes(logger, []); + } else { + log = disabledSemanticLoggerProvider; + } + + const extension = new ExtensionService(registry, { + encryptor, + state, + log, + now: Date.now, + }); + + return { + policy, + extension, + log, + }; + }, + deps: [ + LegacyEncryptorProvider, + StateProvider, + PolicyService, + ExtensionRegistry, + LogService, + PlatformUtilsService, + ], + }), + safeProvider({ + provide: GENERATOR_SERVICE_PROVIDER, + useFactory: ( + system: SystemServiceProvider, + random: Randomizer, + encryptor: LegacyEncryptorProvider, + state: StateProvider, + i18n: I18nService, + api: ApiService, + ) => { + const userStateDeps = { + encryptor, + state, + log: system.log, + now: Date.now, + } satisfies UserStateSubjectDependencyProvider; + + const metadata = new providers.GeneratorMetadataProvider( + userStateDeps, + system, + Object.values(BuiltIn), + ); + const profile = new providers.GeneratorProfileProvider(userStateDeps, system.policy); + + const generator: providers.GeneratorDependencyProvider = { + randomizer: random, + client: new RestClient(api, i18n), + i18nService: i18n, + }; + + const userState: UserStateSubjectDependencyProvider = { + encryptor, + state, + log: system.log, + now: Date.now, + }; + + return { + userState, + generator, + profile, + metadata, + } satisfies providers.CredentialGeneratorProviders; + }, + deps: [ + SYSTEM_SERVICE_PROVIDER, + RANDOMIZER, + LegacyEncryptorProvider, + StateProvider, + I18nService, + ApiService, + ], + }), safeProvider({ provide: UserStateSubjectDependencyProvider, useFactory: (encryptor: LegacyEncryptorProvider, state: StateProvider) => @@ -42,19 +173,14 @@ export const RANDOMIZER = new SafeInjectionToken("Randomizer"); encryptor, state, log: disabledSemanticLoggerProvider, + now: Date.now, }), deps: [LegacyEncryptorProvider, StateProvider], }), safeProvider({ provide: CredentialGeneratorService, - useClass: CredentialGeneratorService, - deps: [ - RANDOMIZER, - PolicyService, - ApiService, - I18nService, - UserStateSubjectDependencyProvider, - ], + useClass: DefaultCredentialGeneratorService, + deps: [GENERATOR_SERVICE_PROVIDER, SYSTEM_SERVICE_PROVIDER], }), ], }) diff --git a/libs/tools/generator/components/src/passphrase-settings.component.ts b/libs/tools/generator/components/src/passphrase-settings.component.ts index 405914977c5..b3525251392 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.ts +++ b/libs/tools/generator/components/src/passphrase-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { OnInit, @@ -12,14 +10,20 @@ import { OnChanges, } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { skip, takeUntil, Subject, map, withLatestFrom, ReplaySubject } from "rxjs"; +import { skip, takeUntil, Subject, map, withLatestFrom, ReplaySubject, tap } from "rxjs"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { + SemanticLogger, + disabledSemanticLoggerProvider, + ifEnabledSemanticLoggerProvider, +} from "@bitwarden/common/tools/log"; import { - Generators, CredentialGeneratorService, PassphraseGenerationOptions, + BuiltIn, } from "@bitwarden/generator-core"; const Controls = Object.freeze({ @@ -45,12 +49,26 @@ export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy private formBuilder: FormBuilder, private generatorService: CredentialGeneratorService, private i18nService: I18nService, + private logService: LogService, ) {} + /** Send structured debug logs from the credential generator component + * to the debugger console. + * + * @warning this may reveal sensitive information in plaintext. + */ + @Input() + debug: boolean = false; + + // this `log` initializer is overridden in `ngOnInit` + private log: SemanticLogger = disabledSemanticLoggerProvider({}); + /** Binds the component to a specific user's settings. + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account: Account = null!; protected account$ = new ReplaySubject(1); @@ -70,53 +88,66 @@ export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, - * use `CredentialGeneratorService.settings$(...)` instead. + * use {@link CredentialGeneratorService.settings} instead. */ @Output() readonly onUpdated = new EventEmitter(); protected settings = this.formBuilder.group({ - [Controls.numWords]: [Generators.passphrase.settings.initial.numWords], - [Controls.wordSeparator]: [Generators.passphrase.settings.initial.wordSeparator], - [Controls.capitalize]: [Generators.passphrase.settings.initial.capitalize], - [Controls.includeNumber]: [Generators.passphrase.settings.initial.includeNumber], + [Controls.numWords]: [0], + [Controls.wordSeparator]: [""], + [Controls.capitalize]: [false], + [Controls.includeNumber]: [false], }); async ngOnInit() { - const settings = await this.generatorService.settings(Generators.passphrase, { + this.log = ifEnabledSemanticLoggerProvider(this.debug, this.logService, { + type: "PassphraseSettingsComponent", + }); + + const settings = await this.generatorService.settings(BuiltIn.passphrase, { account$: this.account$, }); // skips reactive event emissions to break a subscription cycle settings.withConstraints$ - .pipe(takeUntil(this.destroyed$)) + .pipe( + tap((content) => this.log.debug(content, "passphrase settings loaded with constraints")), + takeUntil(this.destroyed$), + ) .subscribe(({ state, constraints }) => { this.settings.patchValue(state, { emitEvent: false }); let boundariesHint = this.i18nService.t( "spinboxBoundariesHint", - constraints.numWords.min?.toString(), - constraints.numWords.max?.toString(), + constraints.numWords?.min?.toString(), + constraints.numWords?.max?.toString(), ); - if (state.numWords <= (constraints.numWords.recommendation ?? 0)) { + if ((state.numWords ?? 0) <= (constraints.numWords?.recommendation ?? 0)) { boundariesHint += this.i18nService.t( "passphraseNumWordsRecommendationHint", - constraints.numWords.recommendation?.toString(), + constraints.numWords?.recommendation?.toString(), ); } this.numWordsBoundariesHint.next(boundariesHint); }); // the first emission is the current value; subsequent emissions are updates - settings.pipe(skip(1), takeUntil(this.destroyed$)).subscribe(this.onUpdated); + settings + .pipe( + skip(1), + tap((settings) => this.log.debug(settings, "passphrase settings onUpdate event")), + takeUntil(this.destroyed$), + ) + .subscribe(this.onUpdated); // explain policy & disable policy-overridden fields this.generatorService - .policy$(Generators.passphrase, { account$: this.account$ }) + .policy$(BuiltIn.passphrase, { account$: this.account$ }) .pipe(takeUntil(this.destroyed$)) .subscribe(({ constraints }) => { - this.wordSeparatorMaxLength = constraints.wordSeparator.maxLength; - this.policyInEffect = constraints.policyInEffect; + this.wordSeparatorMaxLength = constraints.wordSeparator?.maxLength ?? 0; + this.policyInEffect = constraints.policyInEffect ?? false; this.toggleEnabled(Controls.capitalize, !constraints.capitalize?.readonly); this.toggleEnabled(Controls.includeNumber, !constraints.includeNumber?.readonly); @@ -126,22 +157,25 @@ export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy this.saveSettings .pipe( withLatestFrom(this.settings.valueChanges), - map(([, settings]) => settings), + tap(([source, form]) => + this.log.debug({ source, form }, "save passphrase settings request"), + ), + map(([, settings]) => settings as PassphraseGenerationOptions), takeUntil(this.destroyed$), ) .subscribe(settings); } /** attribute binding for wordSeparator[maxlength] */ - protected wordSeparatorMaxLength: number; + protected wordSeparatorMaxLength: number = 0; private saveSettings = new Subject(); - save(site: string = "component api call") { - this.saveSettings.next(site); + save(source: string = "component api call") { + this.saveSettings.next(source); } /** display binding for enterprise policy notice */ - protected policyInEffect: boolean; + protected policyInEffect: boolean = false; private numWordsBoundariesHint = new ReplaySubject(1); @@ -150,9 +184,9 @@ export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy private toggleEnabled(setting: keyof typeof Controls, enabled: boolean) { if (enabled) { - this.settings.get(setting).enable({ emitEvent: false }); + this.settings.get(setting)?.enable({ emitEvent: false }); } else { - this.settings.get(setting).disable({ emitEvent: false }); + this.settings.get(setting)?.disable({ emitEvent: false }); } } diff --git a/libs/tools/generator/components/src/password-generator.component.html b/libs/tools/generator/components/src/password-generator.component.html index 41ed982a490..cc5bdba6062 100644 --- a/libs/tools/generator/components/src/password-generator.component.html +++ b/libs/tools/generator/components/src/password-generator.component.html @@ -3,6 +3,7 @@ class="tw-mb-4" [selected]="credentialType$ | async" (selectedChange)="onCredentialTypeChanged($event)" + *ngIf="showCredentialTypes$ | async" attr.aria-label="{{ 'type' | i18n }}" > @@ -38,14 +39,14 @@ (1); @@ -87,7 +94,11 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy async ngOnChanges(changes: SimpleChanges) { const account = changes?.account; - if (account?.previousValue?.id !== account?.currentValue?.id) { + if ( + account && + account.currentValue.id && + account.previousValue.id !== account.currentValue.id + ) { this.log.debug( { previousUserId: account?.previousValue?.id as UserId, @@ -95,15 +106,19 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy }, "account input change detected", ); - this.account$.next(this.account); + this.account$.next(account.currentValue.id); } } + @Input() + profile: GeneratorProfile = Profile.account; + /** Removes bottom margin, passed to downstream components */ - @Input({ transform: coerceBooleanProperty }) disableMargin = false; + @Input({ transform: coerceBooleanProperty }) + disableMargin = false; /** tracks the currently selected credential type */ - protected credentialType$ = new BehaviorSubject(null); + protected credentialType$ = new BehaviorSubject(null); /** Emits the last generated value. */ protected readonly value$ = new BehaviorSubject(""); @@ -119,9 +134,11 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy * origin in the debugger. */ protected async generate(source: string) { - this.log.debug({ source }, "generation requested"); + const algorithm = await firstValueFrom(this.algorithm$); + const request: GenerateRequest = { source, algorithm: algorithm.id, profile: this.profile }; - this.generate$.next({ source }); + this.log.debug(request, "generation requested"); + this.generate$.next(request); } /** Tracks changes to the selected credential type @@ -146,16 +163,17 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy async ngOnInit() { this.log = ifEnabledSemanticLoggerProvider(this.debug, this.logService, { - type: "UsernameGeneratorComponent", + type: "PasswordGeneratorComponent", }); if (!this.account) { - this.account = await firstValueFrom(this.accountService.activeAccount$); - this.log.info( - { userId: this.account.id }, - "account not specified; using active account settings", - ); - this.account$.next(this.account); + const account = await firstValueFrom(this.accountService.activeAccount$); + if (!account) { + this.log.panic("active account cannot be `null`."); + } + + this.log.info({ userId: account.id }, "account not specified; using active account settings"); + this.account$.next(account); } this.generatorService @@ -167,10 +185,9 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy .subscribe(this.passwordOptions$); // wire up the generator - this.algorithm$ + this.generatorService + .generate$({ on$: this.generate$, account$: this.account$ }) .pipe( - filter((algorithm) => !!algorithm), - switchMap((algorithm) => this.typeToGenerator$(algorithm.id)), catchError((error: unknown, generator) => { if (typeof error === "string") { this.toastService.showToast({ @@ -189,7 +206,7 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy takeUntil(this.destroyed), ) .subscribe(([generated, account, algorithm]) => { - this.log.debug({ source: generated.source }, "credential generated"); + this.log.debug({ source: generated.source ?? null }, "credential generated"); this.generatorHistoryService .track(account.id, generated.credential, generated.category, generated.generationDate) @@ -201,7 +218,7 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy // template bindings refresh immediately this.zone.run(() => { if (generated.source === this.USER_REQUEST) { - this.announce(algorithm.onGeneratedMessage); + this.announce(translate(algorithm.i18nKeys.credentialGenerated, this.i18nService)); } this.onGenerated.next(generated); @@ -219,10 +236,7 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy ) .subscribe(([algorithm, preference]) => { if (isPasswordAlgorithm(algorithm)) { - this.log.info( - { algorithm, category: CredentialCategories.password }, - "algorithm preferences updated", - ); + this.log.info({ algorithm, type: Type.password }, "algorithm preferences updated"); preference.password.algorithm = algorithm; preference.password.updated = new Date(); } else { @@ -236,11 +250,17 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy preferences .pipe( map(({ password }) => this.generatorService.algorithm(password.algorithm)), - distinctUntilChanged((prev, next) => isSameAlgorithm(prev?.id, next?.id)), + distinctUntilChanged((prev, next) => { + if (prev === null || next === null) { + return false; + } else { + return isSameAlgorithm(prev.id, next.id); + } + }), takeUntil(this.destroyed), ) .subscribe((algorithm) => { - this.log.debug(algorithm, "algorithm selected"); + this.log.debug({ algorithm: algorithm.id }, "algorithm selected"); // update navigation this.onCredentialTypeChanged(algorithm.id); @@ -248,22 +268,22 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy // update subjects within the angular zone so that the // template bindings refresh immediately this.zone.run(() => { - this.algorithm$.next(algorithm); - this.onAlgorithm.next(algorithm); + this.maybeAlgorithm$.next(algorithm); + this.onAlgorithm.next(toAlgorithmInfo(algorithm, this.i18nService)); }); }); // generate on load unless the generator prohibits it - this.algorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { + this.maybeAlgorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { this.zone.run(() => { - if (!a || a.onlyOnRequest) { - this.log.debug("autogeneration disabled; clearing generated credential"); - this.value$.next("-"); - } else { + if (a?.capabilities?.autogenerate) { this.log.debug("autogeneration enabled"); this.generate("autogenerate").catch((e: unknown) => { this.log.error(e as object, "a failure occurred during autogeneration"); }); + } else { + this.log.debug("autogeneration disabled; clearing generated credential"); + this.value$.next("-"); } }); }); @@ -275,59 +295,45 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy this.ariaLive.announce(message).catch((e) => this.logService.error(e)); } - private typeToGenerator$(algorithm: CredentialAlgorithm) { - const dependencies = { - on$: this.generate$, - account$: this.account$, - }; - - this.log.debug({ algorithm }, "constructing generation stream"); - - switch (algorithm) { - case "password": - return this.generatorService.generate$(Generators.password, dependencies); - - case "passphrase": - return this.generatorService.generate$(Generators.passphrase, dependencies); - default: - this.log.panic({ algorithm }, `Invalid generator type: "${algorithm}"`); - } - } - /** Lists the credential types supported by the component. */ protected passwordOptions$ = new BehaviorSubject[]>([]); + /** Determines when the password/passphrase selector is visible. */ + protected showCredentialTypes$ = this.passwordOptions$.pipe(map((options) => options.length > 1)); + /** tracks the currently selected credential type */ - protected algorithm$ = new ReplaySubject(1); + protected maybeAlgorithm$ = new ReplaySubject(1); + + /** tracks the last valid algorithm selection */ + protected algorithm$ = this.maybeAlgorithm$.pipe( + filter((algorithm): algorithm is AlgorithmMetadata => !!algorithm), + ); /** * Emits the copy button aria-label respective of the selected credential type */ protected credentialTypeCopyLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ copy }) => copy), + map(({ i18nKeys: { copyCredential } }) => translate(copyCredential, this.i18nService)), ); /** * Emits the generate button aria-label respective of the selected credential type */ protected credentialTypeGenerateLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ generate }) => generate), + map(({ i18nKeys: { generateCredential } }) => translate(generateCredential, this.i18nService)), ); /** * Emits the copy credential toast respective of the selected credential type */ protected credentialTypeLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ credentialType }) => credentialType), + map(({ i18nKeys: { credentialType } }) => translate(credentialType, this.i18nService)), ); - private toOptions(algorithms: AlgorithmInfo[]) { + private toOptions(algorithms: AlgorithmMetadata[]) { const options: Option[] = algorithms.map((algorithm) => ({ value: algorithm.id, - label: algorithm.name, + label: translate(algorithm.i18nKeys.name, this.i18nService), })); return options; diff --git a/libs/tools/generator/components/src/password-settings.component.ts b/libs/tools/generator/components/src/password-settings.component.ts index 346e9549cd8..965ada38146 100644 --- a/libs/tools/generator/components/src/password-settings.component.ts +++ b/libs/tools/generator/components/src/password-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { OnInit, @@ -17,11 +15,13 @@ import { takeUntil, Subject, map, filter, tap, skip, ReplaySubject, withLatestFr import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { - Generators, CredentialGeneratorService, PasswordGenerationOptions, + BuiltIn, } from "@bitwarden/generator-core"; +import { hasRangeOfValues } from "./util"; + const Controls = Object.freeze({ length: "length", uppercase: "uppercase", @@ -52,9 +52,11 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { ) {} /** Binds the component to a specific user's settings. + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account: Account = null!; protected account$ = new ReplaySubject(1); @@ -78,40 +80,40 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, - * use `CredentialGeneratorService.settings$(...)` instead. + * use `CredentialGeneratorService.settings(...)` instead. */ @Output() readonly onUpdated = new EventEmitter(); protected settings = this.formBuilder.group({ - [Controls.length]: [Generators.password.settings.initial.length], - [Controls.uppercase]: [Generators.password.settings.initial.uppercase], - [Controls.lowercase]: [Generators.password.settings.initial.lowercase], - [Controls.number]: [Generators.password.settings.initial.number], - [Controls.special]: [Generators.password.settings.initial.special], - [Controls.minNumber]: [Generators.password.settings.initial.minNumber], - [Controls.minSpecial]: [Generators.password.settings.initial.minSpecial], - [Controls.avoidAmbiguous]: [!Generators.password.settings.initial.ambiguous], + [Controls.length]: [0], + [Controls.uppercase]: [false], + [Controls.lowercase]: [false], + [Controls.number]: [false], + [Controls.special]: [false], + [Controls.minNumber]: [0], + [Controls.minSpecial]: [0], + [Controls.avoidAmbiguous]: [false], }); private get numbers() { - return this.settings.get(Controls.number); + return this.settings.get(Controls.number)!; } private get special() { - return this.settings.get(Controls.special); + return this.settings.get(Controls.special)!; } private get minNumber() { - return this.settings.get(Controls.minNumber); + return this.settings.get(Controls.minNumber)!; } private get minSpecial() { - return this.settings.get(Controls.minSpecial); + return this.settings.get(Controls.minSpecial)!; } async ngOnInit() { - const settings = await this.generatorService.settings(Generators.password, { + const settings = await this.generatorService.settings(BuiltIn.password, { account$: this.account$, }); @@ -130,13 +132,13 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { .subscribe(([state, constraints]) => { let boundariesHint = this.i18nService.t( "spinboxBoundariesHint", - constraints.length.min?.toString(), - constraints.length.max?.toString(), + constraints.length?.min?.toString(), + constraints.length?.max?.toString(), ); - if (state.length <= (constraints.length.recommendation ?? 0)) { + if (state.length <= (constraints.length?.recommendation ?? 0)) { boundariesHint += this.i18nService.t( "passwordLengthRecommendationHint", - constraints.length.recommendation?.toString(), + constraints.length?.recommendation?.toString(), ); } this.lengthBoundariesHint.next(boundariesHint); @@ -147,19 +149,25 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { // explain policy & disable policy-overridden fields this.generatorService - .policy$(Generators.password, { account$: this.account$ }) + .policy$(BuiltIn.password, { account$: this.account$ }) .pipe(takeUntil(this.destroyed$)) .subscribe(({ constraints }) => { - this.policyInEffect = constraints.policyInEffect; + this.policyInEffect = constraints.policyInEffect ?? false; const toggles = [ - [Controls.length, constraints.length.min < constraints.length.max], + [Controls.length, hasRangeOfValues(constraints.length?.min, constraints.length?.max)], [Controls.uppercase, !constraints.uppercase?.readonly], [Controls.lowercase, !constraints.lowercase?.readonly], [Controls.number, !constraints.number?.readonly], [Controls.special, !constraints.special?.readonly], - [Controls.minNumber, constraints.minNumber.min < constraints.minNumber.max], - [Controls.minSpecial, constraints.minSpecial.min < constraints.minSpecial.max], + [ + Controls.minNumber, + hasRangeOfValues(constraints.minNumber?.min, constraints.minNumber?.max), + ], + [ + Controls.minSpecial, + hasRangeOfValues(constraints.minSpecial?.min, constraints.minSpecial?.max), + ], ] as [keyof typeof Controls, boolean][]; for (const [control, enabled] of toggles) { @@ -172,7 +180,7 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { let lastMinNumber = 1; this.numbers.valueChanges .pipe( - filter((checked) => !(checked && this.minNumber.value > 0)), + filter((checked) => !(checked && (this.minNumber.value ?? 0) > 0)), map((checked) => (checked ? lastMinNumber : 0)), takeUntil(this.destroyed$), ) @@ -180,8 +188,11 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { this.minNumber.valueChanges .pipe( - map((value) => [value, value > 0] as const), - tap(([value, checkNumbers]) => (lastMinNumber = checkNumbers ? value : lastMinNumber)), + map((value) => [value, (value ?? 0) > 0] as const), + tap( + ([value, checkNumbers]) => + (lastMinNumber = checkNumbers && value ? value : lastMinNumber), + ), takeUntil(this.destroyed$), ) .subscribe(([, checkNumbers]) => this.numbers.setValue(checkNumbers, { emitEvent: false })); @@ -189,7 +200,7 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { let lastMinSpecial = 1; this.special.valueChanges .pipe( - filter((checked) => !(checked && this.minSpecial.value > 0)), + filter((checked) => !(checked && (this.minSpecial.value ?? 0) > 0)), map((checked) => (checked ? lastMinSpecial : 0)), takeUntil(this.destroyed$), ) @@ -197,8 +208,11 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { this.minSpecial.valueChanges .pipe( - map((value) => [value, value > 0] as const), - tap(([value, checkSpecial]) => (lastMinSpecial = checkSpecial ? value : lastMinSpecial)), + map((value) => [value, (value ?? 0) > 0] as const), + tap( + ([value, checkSpecial]) => + (lastMinSpecial = checkSpecial && value ? value : lastMinSpecial), + ), takeUntil(this.destroyed$), ) .subscribe(([, checkSpecial]) => this.special.setValue(checkSpecial, { emitEvent: false })); @@ -230,7 +244,7 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { } /** display binding for enterprise policy notice */ - protected policyInEffect: boolean; + protected policyInEffect: boolean = false; private lengthBoundariesHint = new ReplaySubject(1); @@ -239,9 +253,9 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { private toggleEnabled(setting: keyof typeof Controls, enabled: boolean) { if (enabled) { - this.settings.get(setting).enable({ emitEvent: false }); + this.settings.get(setting)?.enable({ emitEvent: false }); } else { - this.settings.get(setting).disable({ emitEvent: false }); + this.settings.get(setting)?.disable({ emitEvent: false }); } } diff --git a/libs/tools/generator/components/src/subaddress-settings.component.ts b/libs/tools/generator/components/src/subaddress-settings.component.ts index b09ecc86f9e..27ed6d5f9f3 100644 --- a/libs/tools/generator/components/src/subaddress-settings.component.ts +++ b/libs/tools/generator/components/src/subaddress-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, EventEmitter, @@ -13,10 +11,10 @@ import { import { FormBuilder } from "@angular/forms"; import { map, ReplaySubject, skip, Subject, takeUntil, withLatestFrom } from "rxjs"; -import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { CredentialGeneratorService, - Generators, + BuiltIn, SubaddressGenerationOptions, } from "@bitwarden/generator-core"; @@ -28,20 +26,20 @@ import { }) export class SubaddressSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component - * @param accountService queries user availability * @param generatorService settings and policy logic * @param formBuilder reactive form controls */ constructor( private formBuilder: FormBuilder, private generatorService: CredentialGeneratorService, - private accountService: AccountService, ) {} /** Binds the component to a specific user's settings. + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account: Account = null!; protected account$ = new ReplaySubject(1); @@ -54,18 +52,18 @@ export class SubaddressSettingsComponent implements OnInit, OnChanges, OnDestroy /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, - * use `CredentialGeneratorService.settings$(...)` instead. + * use `CredentialGeneratorService.settings(...)` instead. */ @Output() readonly onUpdated = new EventEmitter(); /** The template's control bindings */ protected settings = this.formBuilder.group({ - subaddressEmail: [Generators.subaddress.settings.initial.subaddressEmail], + subaddressEmail: [""], }); async ngOnInit() { - const settings = await this.generatorService.settings(Generators.subaddress, { + const settings = await this.generatorService.settings(BuiltIn.plusAddress, { account$: this.account$, }); @@ -79,7 +77,7 @@ export class SubaddressSettingsComponent implements OnInit, OnChanges, OnDestroy this.saveSettings .pipe( withLatestFrom(this.settings.valueChanges), - map(([, settings]) => settings), + map(([, settings]) => settings as SubaddressGenerationOptions), takeUntil(this.destroyed$), ) .subscribe(settings); diff --git a/libs/tools/generator/components/src/username-generator.component.html b/libs/tools/generator/components/src/username-generator.component.html index 533a7a0e543..51b998f1d56 100644 --- a/libs/tools/generator/components/src/username-generator.component.html +++ b/libs/tools/generator/components/src/username-generator.component.html @@ -59,7 +59,7 @@ @@ -69,12 +69,12 @@ [account]="account$ | async" /> diff --git a/libs/tools/generator/components/src/username-generator.component.ts b/libs/tools/generator/components/src/username-generator.component.ts index de48a9bd6b1..6227bcd3f7c 100644 --- a/libs/tools/generator/components/src/username-generator.component.ts +++ b/libs/tools/generator/components/src/username-generator.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { LiveAnnouncer } from "@angular/cdk/a11y"; import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { @@ -25,15 +23,15 @@ import { map, ReplaySubject, Subject, - switchMap, takeUntil, + tap, withLatestFrom, } from "rxjs"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { IntegrationId } from "@bitwarden/common/tools/integration"; +import { VendorId } from "@bitwarden/common/tools/extension"; import { SemanticLogger, disabledSemanticLoggerProvider, @@ -43,21 +41,23 @@ import { UserId } from "@bitwarden/common/types/guid"; import { ToastService, Option } from "@bitwarden/components"; import { AlgorithmInfo, - CredentialAlgorithm, - CredentialCategories, CredentialGeneratorService, GenerateRequest, GeneratedCredential, - Generators, - getForwarderConfiguration, + isForwarderExtensionId, isEmailAlgorithm, - isForwarderIntegration, - isSameAlgorithm, isUsernameAlgorithm, - toCredentialGeneratorConfiguration, + isSameAlgorithm, + CredentialAlgorithm, + AlgorithmMetadata, + AlgorithmsByType, + Type, + Algorithm, } from "@bitwarden/generator-core"; import { GeneratorHistoryService } from "@bitwarden/generator-history"; +import { toAlgorithmInfo, translate } from "./util"; + // constants used to identify navigation selections that are not // generator algorithms const FORWARDER = "forwarder"; @@ -89,11 +89,14 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy private ariaLive: LiveAnnouncer, ) {} + /** exports algorithm symbols to the template */ + protected readonly Algorithm = Algorithm; + /** Binds the component to a specific user's settings. When this input is not provided, * the form binds to the active user */ @Input() - account: Account | null; + account: Account | null = null; protected account$ = new ReplaySubject(1); @@ -110,7 +113,11 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy async ngOnChanges(changes: SimpleChanges) { const account = changes?.account; - if (account?.previousValue?.id !== account?.currentValue?.id) { + if ( + account && + account.currentValue.id && + account.previousValue.id !== account.currentValue.id + ) { this.log.debug( { previousUserId: account?.previousValue?.id as UserId, @@ -118,7 +125,7 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy }, "account input change detected", ); - this.account$.next(this.account); + this.account$.next(account.currentValue.id); } } @@ -134,18 +141,18 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy /** emits algorithm info when the selected algorithm changes */ @Output() - readonly onAlgorithm = new EventEmitter(); + readonly onAlgorithm = new EventEmitter(); /** Removes bottom margin from internal elements */ @Input({ transform: coerceBooleanProperty }) disableMargin = false; /** Tracks the selected generation algorithm */ protected username = this.formBuilder.group({ - nav: [null as string], + nav: [null as string | null], }); protected forwarder = this.formBuilder.group({ - nav: [null as string], + nav: [null as string | null], }); async ngOnInit() { @@ -154,38 +161,63 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy }); if (!this.account) { - this.account = await firstValueFrom(this.accountService.activeAccount$); - this.log.info( - { userId: this.account.id }, - "account not specified; using active account settings", - ); - this.account$.next(this.account); + const account = await firstValueFrom(this.accountService.activeAccount$); + if (!account) { + this.log.panic("active account cannot be `null`."); + } + + this.log.info({ userId: account.id }, "account not specified; using active account settings"); + this.account$.next(account); } - this.generatorService - .algorithms$(["email", "username"], { account$: this.account$ }) + combineLatest([ + this.generatorService.algorithms$("email", { account$: this.account$ }), + this.generatorService.algorithms$("username", { account$: this.account$ }), + ]) .pipe( + map((algorithms) => algorithms.flat()), map((algorithms) => { - const usernames = algorithms.filter((a) => !isForwarderIntegration(a.id)); + // construct options for username and email algorithms; replace forwarder + // entry with a virtual entry for drill-down + const usernames = algorithms.filter((a) => !isForwarderExtensionId(a.id)); + usernames.sort((a, b) => a.weight - b.weight); const usernameOptions = this.toOptions(usernames); - usernameOptions.push({ value: FORWARDER, label: this.i18nService.t("forwardedEmail") }); + usernameOptions.splice(-1, 0, { + value: FORWARDER, + label: this.i18nService.t("forwardedEmail"), + }); - const forwarders = algorithms.filter((a) => isForwarderIntegration(a.id)); + // construct options for forwarder algorithms; they get their own selection box + const forwarders = algorithms.filter((a) => isForwarderExtensionId(a.id)); + forwarders.sort((a, b) => a.weight - b.weight); const forwarderOptions = this.toOptions(forwarders); forwarderOptions.unshift({ value: NONE_SELECTED, label: this.i18nService.t("select") }); return [usernameOptions, forwarderOptions] as const; }), + tap((algorithms) => + this.log.debug({ algorithms: algorithms as object }, "algorithms loaded"), + ), takeUntil(this.destroyed), ) .subscribe(([usernames, forwarders]) => { - this.typeOptions$.next(usernames); - this.forwarderOptions$.next(forwarders); + // update subjects within the angular zone so that the + // template bindings refresh immediately + this.zone.run(() => { + this.typeOptions$.next(usernames); + this.forwarderOptions$.next(forwarders); + }); }); - this.algorithm$ + this.maybeAlgorithm$ .pipe( - map((a) => a?.description), + map((a) => { + if (a?.i18nKeys?.description) { + return translate(a.i18nKeys.description, this.i18nService); + } else { + return ""; + } + }), takeUntil(this.destroyed), ) .subscribe((hint) => { @@ -197,10 +229,12 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy }); // wire up the generator - this.algorithm$ + this.generatorService + .generate$({ + on$: this.generate$, + account$: this.account$, + }) .pipe( - filter((algorithm) => !!algorithm), - switchMap((algorithm) => this.typeToGenerator$(algorithm.id)), catchError((error: unknown, generator) => { if (typeof error === "string") { this.toastService.showToast({ @@ -215,11 +249,14 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy // continue with origin stream return generator; }), - withLatestFrom(this.account$, this.algorithm$), + withLatestFrom(this.account$, this.maybeAlgorithm$), takeUntil(this.destroyed), ) .subscribe(([generated, account, algorithm]) => { - this.log.debug({ source: generated.source }, "credential generated"); + this.log.debug( + { source: generated.source ?? null, algorithm: algorithm?.id ?? null }, + "credential generated", + ); this.generatorHistoryService .track(account.id, generated.credential, generated.category, generated.generationDate) @@ -230,12 +267,12 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy // update subjects within the angular zone so that the // template bindings refresh immediately this.zone.run(() => { - if (generated.source === this.USER_REQUEST) { - this.announce(algorithm.onGeneratedMessage); + if (algorithm && generated.source === this.USER_REQUEST) { + this.announce(translate(algorithm.i18nKeys.credentialGenerated, this.i18nService)); } + this.generatedCredential$.next(generated); this.onGenerated.next(generated); - this.value$.next(generated.credential); }); }); @@ -248,24 +285,31 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy this.username.valueChanges .pipe( - map( - (username): CascadeValue => - username.nav === FORWARDER - ? { nav: username.nav } - : { nav: username.nav, algorithm: JSON.parse(username.nav) }, - ), + map((username): CascadeValue => { + if (username.nav === FORWARDER) { + return { nav: username.nav }; + } else if (username.nav) { + return { nav: username.nav, algorithm: JSON.parse(username.nav) }; + } else { + const [algorithm] = AlgorithmsByType[Type.username]; + return { nav: JSON.stringify(algorithm), algorithm }; + } + }), takeUntil(this.destroyed), ) .subscribe(activeIdentifier$); this.forwarder.valueChanges .pipe( - map( - (forwarder): CascadeValue => - forwarder.nav === NONE_SELECTED - ? { nav: forwarder.nav } - : { nav: forwarder.nav, algorithm: JSON.parse(forwarder.nav) }, - ), + map((forwarder): CascadeValue => { + if (forwarder.nav === NONE_SELECTED) { + return { nav: forwarder.nav }; + } else if (forwarder.nav) { + return { nav: forwarder.nav, algorithm: JSON.parse(forwarder.nav) }; + } else { + return { nav: NONE_SELECTED }; + } + }), takeUntil(this.destroyed), ) .subscribe(activeForwarder$); @@ -276,7 +320,7 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy map(([username, forwarder]) => { const showForwarder = !username.algorithm; const forwarderId = - showForwarder && isForwarderIntegration(forwarder.algorithm) + showForwarder && forwarder.algorithm && isForwarderExtensionId(forwarder.algorithm) ? forwarder.algorithm.forwarder : null; return [showForwarder, forwarderId] as const; @@ -306,57 +350,61 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy return null; } }), - distinctUntilChanged((prev, next) => isSameAlgorithm(prev?.id, next?.id)), + distinctUntilChanged((prev, next) => { + if (prev === null || next === null) { + return false; + } else { + return isSameAlgorithm(prev.id, next.id); + } + }), takeUntil(this.destroyed), ) .subscribe((algorithm) => { - this.log.debug(algorithm, "algorithm selected"); + this.log.debug({ algorithm: algorithm?.id ?? null }, "algorithm selected"); // update subjects within the angular zone so that the // template bindings refresh immediately this.zone.run(() => { - this.algorithm$.next(algorithm); - this.onAlgorithm.next(algorithm); + this.maybeAlgorithm$.next(algorithm); + if (algorithm) { + this.onAlgorithm.next(toAlgorithmInfo(algorithm, this.i18nService)); + } else { + this.onAlgorithm.next(null); + } }); }); // assume the last-visible generator algorithm is the user's preferred one const preferences = await this.generatorService.preferences({ account$: this.account$ }); this.algorithm$ - .pipe( - filter((algorithm) => !!algorithm), - withLatestFrom(preferences), - takeUntil(this.destroyed), - ) + .pipe(withLatestFrom(preferences), takeUntil(this.destroyed)) .subscribe(([algorithm, preference]) => { if (isEmailAlgorithm(algorithm.id)) { - this.log.info( - { algorithm, category: CredentialCategories.email }, - "algorithm preferences updated", - ); preference.email.algorithm = algorithm.id; preference.email.updated = new Date(); } else if (isUsernameAlgorithm(algorithm.id)) { - this.log.info( - { algorithm, category: CredentialCategories.username }, - "algorithm preferences updated", - ); preference.username.algorithm = algorithm.id; preference.username.updated = new Date(); } else { return; } + this.log.info( + { algorithm: algorithm.id, type: algorithm.type }, + "algorithm preferences updated", + ); preferences.next(preference); }); preferences .pipe( map(({ email, username }) => { - const forwarderPref = isForwarderIntegration(email.algorithm) ? email : null; const usernamePref = email.updated > username.updated ? email : username; + const forwarderPref = isForwarderExtensionId(usernamePref.algorithm) + ? usernamePref + : null; - // inject drilldown flags + // inject drill-down flags const forwarderNav = !forwarderPref ? NONE_SELECTED : JSON.stringify(forwarderPref.algorithm); @@ -368,7 +416,7 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy selection: { nav: userNav }, active: { nav: userNav, - algorithm: forwarderPref ? null : usernamePref.algorithm, + algorithm: forwarderPref ? undefined : usernamePref.algorithm, }, }, forwarder: { @@ -385,6 +433,14 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy takeUntil(this.destroyed), ) .subscribe(({ username, forwarder }) => { + this.log.debug( + { + username: username.selection, + forwarder: forwarder.selection, + }, + "navigation updated", + ); + // update navigation; break subscription loop this.username.setValue(username.selection, { emitEvent: false }); this.forwarder.setValue(forwarder.selection, { emitEvent: false }); @@ -396,17 +452,16 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy // automatically regenerate when the algorithm switches if the algorithm // allows it; otherwise set a placeholder - this.algorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { + this.maybeAlgorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { this.zone.run(() => { - if (!a || a.onlyOnRequest) { - this.log.debug("autogeneration disabled; clearing generated credential"); - this.value$.next("-"); - } else { + if (a?.capabilities?.autogenerate) { this.log.debug("autogeneration enabled"); - this.generate("autogenerate").catch((e: unknown) => { this.log.error(e as object, "a failure occurred during autogeneration"); }); + } else { + this.log.debug("autogeneration disabled; clearing generated credential"); + this.generatedCredential$.next(undefined); } }); }); @@ -414,34 +469,6 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy this.log.debug("component initialized"); } - private typeToGenerator$(algorithm: CredentialAlgorithm) { - const dependencies = { - on$: this.generate$, - account$: this.account$, - }; - - this.log.debug({ algorithm }, "constructing generation stream"); - - switch (algorithm) { - case "catchall": - return this.generatorService.generate$(Generators.catchall, dependencies); - - case "subaddress": - return this.generatorService.generate$(Generators.subaddress, dependencies); - - case "username": - return this.generatorService.generate$(Generators.username, dependencies); - } - - if (isForwarderIntegration(algorithm)) { - const forwarder = getForwarderConfiguration(algorithm.forwarder); - const configuration = toCredentialGeneratorConfiguration(forwarder); - return this.generatorService.generate$(configuration, dependencies); - } - - this.log.panic({ algorithm }, `Invalid generator type: "${algorithm}"`); - } - private announce(message: string) { this.ariaLive.announce(message).catch((e) => this.logService.error(e)); } @@ -450,7 +477,7 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy protected typeOptions$ = new BehaviorSubject[]>([]); /** Tracks the currently selected forwarder. */ - protected forwarderId$ = new BehaviorSubject(null); + protected forwarderId$ = new BehaviorSubject(null); /** Lists the credential types supported by the component. */ protected forwarderOptions$ = new BehaviorSubject[]>([]); @@ -458,19 +485,30 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy /** Tracks forwarder control visibility */ protected showForwarder$ = new BehaviorSubject(false); - /** tracks the currently selected credential type */ - protected algorithm$ = new ReplaySubject(1); + /** tracks the currently selected algorithm; emits `null` when no algorithm selected */ + protected maybeAlgorithm$ = new ReplaySubject(1); + + /** tracks the last valid algorithm selection */ + protected algorithm$ = this.maybeAlgorithm$.pipe( + filter((algorithm): algorithm is AlgorithmMetadata => !!algorithm), + ); /** Emits hint key for the currently selected credential type */ protected credentialTypeHint$ = new ReplaySubject(1); + private readonly generatedCredential$ = new BehaviorSubject( + undefined, + ); + /** Emits the last generated value. */ - protected readonly value$ = new BehaviorSubject(""); + protected readonly value$ = this.generatedCredential$.pipe( + map((generated) => generated?.credential ?? "-"), + ); /** Emits when a new credential is requested */ private readonly generate$ = new Subject(); - protected showAlgorithm$ = this.algorithm$.pipe( + protected showAlgorithm$ = this.maybeAlgorithm$.pipe( combineLatestWith(this.showForwarder$), map(([algorithm, showForwarder]) => (showForwarder ? null : algorithm)), ); @@ -479,24 +517,21 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy * Emits the copy button aria-label respective of the selected credential type */ protected credentialTypeCopyLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ copy }) => copy), + map(({ i18nKeys: { copyCredential } }) => translate(copyCredential, this.i18nService)), ); /** * Emits the generate button aria-label respective of the selected credential type */ protected credentialTypeGenerateLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ generate }) => generate), + map(({ i18nKeys: { generateCredential } }) => translate(generateCredential, this.i18nService)), ); /** * Emits the copy credential toast respective of the selected credential type */ protected credentialTypeLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ credentialType }) => credentialType), + map(({ i18nKeys: { credentialType } }) => translate(credentialType, this.i18nService)), ); /** Identifies generator requests that were requested by the user */ @@ -507,15 +542,20 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy * origin in the debugger. */ protected async generate(source: string) { - const request = { source, website: this.website }; + const algorithm = await firstValueFrom(this.algorithm$); + const request: GenerateRequest = { source, algorithm: algorithm.id }; + if (this.website) { + request.website = this.website; + } + this.log.debug(request, "generation requested"); this.generate$.next(request); } - private toOptions(algorithms: AlgorithmInfo[]) { + private toOptions(algorithms: AlgorithmMetadata[]) { const options: Option[] = algorithms.map((algorithm) => ({ value: JSON.stringify(algorithm.id), - label: algorithm.name, + label: translate(algorithm.i18nKeys.name, this.i18nService), })); return options; @@ -528,9 +568,11 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy // finalize subjects this.generate$.complete(); - this.value$.complete(); + this.generatedCredential$.complete(); // finalize component bindings this.onGenerated.complete(); + + this.log.debug("component destroyed"); } } diff --git a/libs/tools/generator/components/src/username-settings.component.ts b/libs/tools/generator/components/src/username-settings.component.ts index ea3cfbd35fb..7a12957f906 100644 --- a/libs/tools/generator/components/src/username-settings.component.ts +++ b/libs/tools/generator/components/src/username-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, EventEmitter, @@ -17,7 +15,7 @@ import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { CredentialGeneratorService, EffUsernameGenerationOptions, - Generators, + BuiltIn, } from "@bitwarden/generator-core"; /** Options group for usernames */ @@ -28,7 +26,6 @@ import { }) export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component - * @param accountService queries user availability * @param generatorService settings and policy logic * @param formBuilder reactive form controls */ @@ -38,9 +35,11 @@ export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { ) {} /** Binds the component to a specific user's settings. + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account: Account = null!; protected account$ = new ReplaySubject(1); @@ -53,19 +52,19 @@ export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, - * use `CredentialGeneratorService.settings$(...)` instead. + * use `CredentialGeneratorService.settings(...)` instead. */ @Output() readonly onUpdated = new EventEmitter(); /** The template's control bindings */ protected settings = this.formBuilder.group({ - wordCapitalize: [Generators.username.settings.initial.wordCapitalize], - wordIncludeNumber: [Generators.username.settings.initial.wordIncludeNumber], + wordCapitalize: [false], + wordIncludeNumber: [false], }); async ngOnInit() { - const settings = await this.generatorService.settings(Generators.username, { + const settings = await this.generatorService.settings(BuiltIn.effWordList, { account$: this.account$, }); @@ -79,7 +78,7 @@ export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { this.saveSettings .pipe( withLatestFrom(this.settings.valueChanges), - map(([, settings]) => settings), + map(([, settings]) => settings as EffUsernameGenerationOptions), takeUntil(this.destroyed$), ) .subscribe(settings); diff --git a/libs/tools/generator/components/src/util.ts b/libs/tools/generator/components/src/util.ts index 95e55d816ce..4b0ce4383de 100644 --- a/libs/tools/generator/components/src/util.ts +++ b/libs/tools/generator/components/src/util.ts @@ -1,71 +1,46 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ValidatorFn, Validators } from "@angular/forms"; -import { distinctUntilChanged, map, pairwise, pipe, skipWhile, startWith, takeWhile } from "rxjs"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nKeyOrLiteral } from "@bitwarden/common/tools/types"; +import { isI18nKey } from "@bitwarden/common/tools/util"; +import { AlgorithmInfo, AlgorithmMetadata } from "@bitwarden/generator-core"; -import { AnyConstraint, Constraints } from "@bitwarden/common/tools/types"; -import { UserId } from "@bitwarden/common/types/guid"; -import { CredentialGeneratorConfiguration } from "@bitwarden/generator-core"; +/** Adapts {@link AlgorithmMetadata} to legacy {@link AlgorithmInfo} structure. */ +export function toAlgorithmInfo(metadata: AlgorithmMetadata, i18n: I18nService) { + const info: AlgorithmInfo = { + id: metadata.id, + type: metadata.type, + name: translate(metadata.i18nKeys.name, i18n), + generate: translate(metadata.i18nKeys.generateCredential, i18n), + onGeneratedMessage: translate(metadata.i18nKeys.credentialGenerated, i18n), + credentialType: translate(metadata.i18nKeys.credentialType, i18n), + copy: translate(metadata.i18nKeys.copyCredential, i18n), + useGeneratedValue: translate(metadata.i18nKeys.useCredential, i18n), + onlyOnRequest: !metadata.capabilities.autogenerate, + request: metadata.capabilities.fields, + }; -export function completeOnAccountSwitch() { - return pipe( - map(({ id }: { id: UserId | null }) => id), - skipWhile((id) => !id), - startWith(null as UserId), - pairwise(), - takeWhile(([prev, next]) => (prev ?? next) === next), - map(([_, id]) => id), - distinctUntilChanged(), - ); + if (metadata.i18nKeys.description) { + info.description = translate(metadata.i18nKeys.description, i18n); + } + + return info; } -export function toValidators( - target: keyof Settings, - configuration: CredentialGeneratorConfiguration, - policy?: Constraints, -) { - const validators: Array = []; - - // widen the types to avoid typecheck issues - const config: AnyConstraint = configuration.settings.constraints[target]; - const runtime: AnyConstraint = policy[target]; - - const required = getConstraint("required", config, runtime) ?? false; - if (required) { - validators.push(Validators.required); - } - - const maxLength = getConstraint("maxLength", config, runtime); - if (maxLength !== undefined) { - validators.push(Validators.maxLength(maxLength)); - } - - const minLength = getConstraint("minLength", config, runtime); - if (minLength !== undefined) { - validators.push(Validators.minLength(config.minLength)); - } - - const min = getConstraint("min", config, runtime); - if (min !== undefined) { - validators.push(Validators.min(min)); - } - - const max = getConstraint("max", config, runtime); - if (max !== undefined) { - validators.push(Validators.max(max)); - } - - return validators; +/** Translates an internationalization key + * @param key the key to translate + * @param i18n the service providing translations + * @returns the translated key; if the key is a literal the literal + * is returned instead. + */ +export function translate(key: I18nKeyOrLiteral, i18n: I18nService) { + return isI18nKey(key) ? i18n.t(key) : key.literal; } -function getConstraint( - key: Key, - config: AnyConstraint, - policy?: AnyConstraint, -) { - if (policy && key in policy) { - return policy[key] ?? config[key]; - } else if (config && key in config) { - return config[key]; - } +/** Returns true when min < max + * @param min the minimum value to check; when this is nullish it becomes 0. + * @param max the maximum value to check; when this is nullish it becomes +Infinity. + */ +export function hasRangeOfValues(min?: number, max?: number) { + const minimum = min ?? 0; + const maximum = max ?? Number.POSITIVE_INFINITY; + return minimum < maximum; } diff --git a/libs/tools/generator/core/src/abstractions/credential-generator-service.abstraction.ts b/libs/tools/generator/core/src/abstractions/credential-generator-service.abstraction.ts new file mode 100644 index 00000000000..74f78514594 --- /dev/null +++ b/libs/tools/generator/core/src/abstractions/credential-generator-service.abstraction.ts @@ -0,0 +1,104 @@ +import { Observable } from "rxjs"; + +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { BoundDependency, OnDependency } from "@bitwarden/common/tools/dependencies"; +import { VendorId } from "@bitwarden/common/tools/extension"; +import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject"; + +import { + CredentialAlgorithm, + GeneratorMetadata, + GeneratorProfile, + CredentialType, +} from "../metadata"; +import { AlgorithmMetadata } from "../metadata/algorithm-metadata"; +import { + CredentialPreference, + ForwarderOptions, + GeneratedCredential, + GenerateRequest, +} from "../types"; +import { GeneratorConstraints } from "../types/generator-constraints"; + +/** Generates credentials used in identity and/or authentication flows. + */ +export abstract class CredentialGeneratorService { + /** Generates a stream of credentials + * @param dependencies.on$ Required. A new credential is emitted when this emits. + */ + abstract generate$: ( + dependencies: OnDependency & BoundDependency<"account", Account>, + ) => Observable; + + /** Emits metadata for the set of algorithms available to a user. + * @param type the set of algorithms + * @param dependencies.account$ algorithms are filtered to only + * those matching the provided account's policy. + * @returns An observable that emits algorithm metadata. + */ + abstract algorithms$: ( + type: CredentialType, + dependencies: BoundDependency<"account", Account>, + ) => Observable; + + /** Lists metadata for a set of algorithms. + * @param type the type or types of algorithms + * @returns A list containing the requested metadata. + * @remarks this is a raw data interface. To apply rules such as algorithm availability, + * use {@link algorithms$} instead. + */ + abstract algorithms: (type: CredentialType | CredentialType[]) => AlgorithmMetadata[]; + + /** Look up the metadata for a specific generator algorithm + * @param id identifies the algorithm + * @returns the requested metadata, or `null` if the metadata wasn't found. + */ + abstract algorithm: (id: CredentialAlgorithm) => AlgorithmMetadata; + + /** Look up the forwarder metadata for a vendor. + * @param id identifies the vendor proving the forwarder + */ + abstract forwarder: (id: VendorId) => GeneratorMetadata; + + /** Get a subject bound to credential generator preferences. + * @param dependencies.account$ identifies the account to which the preferences are bound + * @returns a subject bound to the user's preferences + * @remarks Preferences determine which algorithms are used when generating a + * credential from a credential category (e.g. `PassX` or `Username`). Preferences + * should not be used to hold navigation history. Use {@link @bitwarden/generator-navigation} + * instead. + */ + abstract preferences: ( + dependencies: BoundDependency<"account", Account>, + ) => UserStateSubject; + + /** Get a subject bound to a specific user's settings. the subject enforces policy for the + * settings by automatically updating incorrect values to those allowed by policy. + * @param metadata determines which generator's settings are loaded + * @param dependencies.account$ identifies the account to which the settings are bound + * @param profile identifies the generator profile to load; when this is not specified + * the user's account profile is loaded. + * @returns a subject bound to the requested user's generator settings + * @remarks Generator metadata can be looked up using {@link BuiltIn} and {@link forwarder}. + */ + abstract settings: ( + metadata: Readonly>, + dependencies: BoundDependency<"account", Account>, + profile?: GeneratorProfile, + ) => UserStateSubject; + + /** Get the policy constraints for the provided configuration + * @param metadata determines which generator's policy is loaded + * @param dependencies.account$ determines which user's policy is loaded + * @param profile identifies the generator profile to load; when this is not specified + * the user's account profile is loaded. + * @returns an observable that emits the policy once `dependencies.account$` + * and the policy become available. + * @remarks Generator metadata can be looked up using {@link BuiltIn} and {@link forwarder}. + */ + abstract policy$: ( + metadata: Readonly>, + dependencies: BoundDependency<"account", Account>, + profile?: GeneratorProfile, + ) => Observable>; +} diff --git a/libs/tools/generator/core/src/abstractions/generator.service.abstraction.ts b/libs/tools/generator/core/src/abstractions/generator.service.abstraction.ts index 221c0b8b007..9c12dba44c7 100644 --- a/libs/tools/generator/core/src/abstractions/generator.service.abstraction.ts +++ b/libs/tools/generator/core/src/abstractions/generator.service.abstraction.ts @@ -9,6 +9,7 @@ import { PolicyEvaluator } from "./policy-evaluator.abstraction"; /** Generates credentials used for user authentication * @typeParam Options the credential generation configuration * @typeParam Policy the policy enforced by the generator + * @deprecated Use {@link CredentialGeneratorService} instead. */ export abstract class GeneratorService { /** An observable monitoring the options saved to disk. diff --git a/libs/tools/generator/core/src/abstractions/index.ts b/libs/tools/generator/core/src/abstractions/index.ts index 471ec89ea32..4f45f985ef2 100644 --- a/libs/tools/generator/core/src/abstractions/index.ts +++ b/libs/tools/generator/core/src/abstractions/index.ts @@ -1,3 +1,4 @@ +export { CredentialGeneratorService } from "./credential-generator-service.abstraction"; export { GeneratorService } from "./generator.service.abstraction"; export { GeneratorStrategy } from "./generator-strategy.abstraction"; export { PolicyEvaluator } from "./policy-evaluator.abstraction"; diff --git a/libs/tools/generator/core/src/data/default-addy-io-options.ts b/libs/tools/generator/core/src/data/default-addy-io-options.ts deleted file mode 100644 index 2ebeefff6a8..00000000000 --- a/libs/tools/generator/core/src/data/default-addy-io-options.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { EmailDomainOptions, SelfHostedApiOptions } from "../types"; - -export const DefaultAddyIoOptions: SelfHostedApiOptions & EmailDomainOptions = Object.freeze({ - website: null, - baseUrl: "https://app.addy.io", - token: "", - domain: "", -}); diff --git a/libs/tools/generator/core/src/data/default-credential-preferences.ts b/libs/tools/generator/core/src/data/default-credential-preferences.ts deleted file mode 100644 index c26d44b3b79..00000000000 --- a/libs/tools/generator/core/src/data/default-credential-preferences.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CredentialPreference } from "../types"; - -import { EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "./generator-types"; - -export const DefaultCredentialPreferences: CredentialPreference = Object.freeze({ - email: Object.freeze({ - algorithm: EmailAlgorithms[0], - updated: new Date(0), - }), - password: Object.freeze({ - algorithm: PasswordAlgorithms[0], - updated: new Date(0), - }), - username: Object.freeze({ - algorithm: UsernameAlgorithms[0], - updated: new Date(0), - }), -}); diff --git a/libs/tools/generator/core/src/data/default-duck-duck-go-options.ts b/libs/tools/generator/core/src/data/default-duck-duck-go-options.ts deleted file mode 100644 index c600e6e512a..00000000000 --- a/libs/tools/generator/core/src/data/default-duck-duck-go-options.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ApiOptions } from "../types"; - -export const DefaultDuckDuckGoOptions: ApiOptions = Object.freeze({ - website: null, - token: "", -}); diff --git a/libs/tools/generator/core/src/data/default-fastmail-options.ts b/libs/tools/generator/core/src/data/default-fastmail-options.ts deleted file mode 100644 index 18faefc4643..00000000000 --- a/libs/tools/generator/core/src/data/default-fastmail-options.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ApiOptions, EmailPrefixOptions } from "../types"; - -export const DefaultFastmailOptions: ApiOptions & EmailPrefixOptions = Object.freeze({ - website: "", - domain: "", - prefix: "", - token: "", -}); diff --git a/libs/tools/generator/core/src/data/default-firefox-relay-options.ts b/libs/tools/generator/core/src/data/default-firefox-relay-options.ts deleted file mode 100644 index 20433a3e12a..00000000000 --- a/libs/tools/generator/core/src/data/default-firefox-relay-options.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ApiOptions } from "../types"; - -export const DefaultFirefoxRelayOptions: ApiOptions = Object.freeze({ - website: null, - token: "", -}); diff --git a/libs/tools/generator/core/src/data/default-forward-email-options.ts b/libs/tools/generator/core/src/data/default-forward-email-options.ts deleted file mode 100644 index d5175534a05..00000000000 --- a/libs/tools/generator/core/src/data/default-forward-email-options.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ApiOptions, EmailDomainOptions } from "../types"; - -export const DefaultForwardEmailOptions: ApiOptions & EmailDomainOptions = Object.freeze({ - website: null, - token: "", - domain: "", -}); diff --git a/libs/tools/generator/core/src/data/default-simple-login-options.ts b/libs/tools/generator/core/src/data/default-simple-login-options.ts deleted file mode 100644 index 965b1222cd3..00000000000 --- a/libs/tools/generator/core/src/data/default-simple-login-options.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SelfHostedApiOptions } from "../types"; - -export const DefaultSimpleLoginOptions: SelfHostedApiOptions = Object.freeze({ - website: null, - baseUrl: "https://app.simplelogin.io", - token: "", -}); diff --git a/libs/tools/generator/core/src/data/generator-types.ts b/libs/tools/generator/core/src/data/generator-types.ts deleted file mode 100644 index e54ec34e497..00000000000 --- a/libs/tools/generator/core/src/data/generator-types.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** Types of passwords that may be generated by the credential generator */ -export const PasswordAlgorithms = Object.freeze(["password", "passphrase"] as const); - -/** Types of usernames that may be generated by the credential generator */ -export const UsernameAlgorithms = Object.freeze(["username"] as const); - -/** Types of email addresses that may be generated by the credential generator */ -export const EmailAlgorithms = Object.freeze(["catchall", "subaddress"] as const); - -/** All types of credentials that may be generated by the credential generator */ -export const CredentialAlgorithms = Object.freeze([ - ...PasswordAlgorithms, - ...UsernameAlgorithms, - ...EmailAlgorithms, -] as const); diff --git a/libs/tools/generator/core/src/data/generators.ts b/libs/tools/generator/core/src/data/generators.ts deleted file mode 100644 index da87c60f1f4..00000000000 --- a/libs/tools/generator/core/src/data/generators.ts +++ /dev/null @@ -1,422 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; -import { ApiSettings } from "@bitwarden/common/tools/integration/rpc"; -import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; -import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; -import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; - -import { - EmailRandomizer, - ForwarderConfiguration, - PasswordRandomizer, - UsernameRandomizer, -} from "../engine"; -import { Forwarder } from "../engine/forwarder"; -import { - DefaultPolicyEvaluator, - DynamicPasswordPolicyConstraints, - PassphraseGeneratorOptionsEvaluator, - passphraseLeastPrivilege, - PassphrasePolicyConstraints, - PasswordGeneratorOptionsEvaluator, - passwordLeastPrivilege, -} from "../policies"; -import { CatchallConstraints } from "../policies/catchall-constraints"; -import { SubaddressConstraints } from "../policies/subaddress-constraints"; -import { - CatchallGenerationOptions, - CredentialGenerator, - CredentialGeneratorConfiguration, - EffUsernameGenerationOptions, - GeneratorDependencyProvider, - NoPolicy, - PassphraseGenerationOptions, - PassphraseGeneratorPolicy, - PasswordGenerationOptions, - PasswordGeneratorPolicy, - SubaddressGenerationOptions, -} from "../types"; - -import { DefaultCatchallOptions } from "./default-catchall-options"; -import { DefaultEffUsernameOptions } from "./default-eff-username-options"; -import { DefaultPassphraseBoundaries } from "./default-passphrase-boundaries"; -import { DefaultPassphraseGenerationOptions } from "./default-passphrase-generation-options"; -import { DefaultPasswordBoundaries } from "./default-password-boundaries"; -import { DefaultPasswordGenerationOptions } from "./default-password-generation-options"; -import { DefaultSubaddressOptions } from "./default-subaddress-generator-options"; - -const PASSPHRASE: CredentialGeneratorConfiguration< - PassphraseGenerationOptions, - PassphraseGeneratorPolicy -> = Object.freeze({ - id: "passphrase", - category: "password", - nameKey: "passphrase", - generateKey: "generatePassphrase", - onGeneratedMessageKey: "passphraseGenerated", - credentialTypeKey: "passphrase", - copyKey: "copyPassphrase", - useGeneratedValueKey: "useThisPassword", - onlyOnRequest: false, - request: [], - engine: { - create( - dependencies: GeneratorDependencyProvider, - ): CredentialGenerator { - return new PasswordRandomizer(dependencies.randomizer); - }, - }, - settings: { - initial: DefaultPassphraseGenerationOptions, - constraints: { - numWords: { - min: DefaultPassphraseBoundaries.numWords.min, - max: DefaultPassphraseBoundaries.numWords.max, - recommendation: DefaultPassphraseGenerationOptions.numWords, - }, - wordSeparator: { maxLength: 1 }, - }, - account: { - key: "passphraseGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "numWords", - "wordSeparator", - "capitalize", - "includeNumber", - ]), - state: GENERATOR_DISK, - initial: DefaultPassphraseGenerationOptions, - options: { - deserializer: (value) => value, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: Object.freeze({ - minNumberWords: 0, - capitalize: false, - includeNumber: false, - }), - combine: passphraseLeastPrivilege, - createEvaluator: (policy) => new PassphraseGeneratorOptionsEvaluator(policy), - toConstraints: (policy) => - new PassphrasePolicyConstraints(policy, PASSPHRASE.settings.constraints), - }, -}); - -const PASSWORD: CredentialGeneratorConfiguration< - PasswordGenerationOptions, - PasswordGeneratorPolicy -> = Object.freeze({ - id: "password", - category: "password", - nameKey: "password", - generateKey: "generatePassword", - onGeneratedMessageKey: "passwordGenerated", - credentialTypeKey: "password", - copyKey: "copyPassword", - useGeneratedValueKey: "useThisPassword", - onlyOnRequest: false, - request: [], - engine: { - create( - dependencies: GeneratorDependencyProvider, - ): CredentialGenerator { - return new PasswordRandomizer(dependencies.randomizer); - }, - }, - settings: { - initial: DefaultPasswordGenerationOptions, - constraints: { - length: { - min: DefaultPasswordBoundaries.length.min, - max: DefaultPasswordBoundaries.length.max, - recommendation: DefaultPasswordGenerationOptions.length, - }, - minNumber: { - min: DefaultPasswordBoundaries.minDigits.min, - max: DefaultPasswordBoundaries.minDigits.max, - }, - minSpecial: { - min: DefaultPasswordBoundaries.minSpecialCharacters.min, - max: DefaultPasswordBoundaries.minSpecialCharacters.max, - }, - }, - account: { - key: "passwordGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "length", - "ambiguous", - "uppercase", - "minUppercase", - "lowercase", - "minLowercase", - "number", - "minNumber", - "special", - "minSpecial", - ]), - state: GENERATOR_DISK, - initial: DefaultPasswordGenerationOptions, - options: { - deserializer: (value) => value, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: Object.freeze({ - minLength: 0, - useUppercase: false, - useLowercase: false, - useNumbers: false, - numberCount: 0, - useSpecial: false, - specialCount: 0, - }), - combine: passwordLeastPrivilege, - createEvaluator: (policy) => new PasswordGeneratorOptionsEvaluator(policy), - toConstraints: (policy) => - new DynamicPasswordPolicyConstraints(policy, PASSWORD.settings.constraints), - }, -}); - -const USERNAME: CredentialGeneratorConfiguration = - Object.freeze({ - id: "username", - category: "username", - nameKey: "randomWord", - generateKey: "generateUsername", - onGeneratedMessageKey: "usernameGenerated", - credentialTypeKey: "username", - copyKey: "copyUsername", - useGeneratedValueKey: "useThisUsername", - onlyOnRequest: false, - request: [], - engine: { - create( - dependencies: GeneratorDependencyProvider, - ): CredentialGenerator { - return new UsernameRandomizer(dependencies.randomizer); - }, - }, - settings: { - initial: DefaultEffUsernameOptions, - constraints: {}, - account: { - key: "effUsernameGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "wordCapitalize", - "wordIncludeNumber", - ]), - state: GENERATOR_DISK, - initial: DefaultEffUsernameOptions, - options: { - deserializer: (value) => value, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: {}, - combine(_acc: NoPolicy, _policy: Policy) { - return {}; - }, - createEvaluator(_policy: NoPolicy) { - return new DefaultPolicyEvaluator(); - }, - toConstraints(_policy: NoPolicy) { - return new IdentityConstraint(); - }, - }, - }); - -const CATCHALL: CredentialGeneratorConfiguration = - Object.freeze({ - id: "catchall", - category: "email", - nameKey: "catchallEmail", - descriptionKey: "catchallEmailDesc", - generateKey: "generateEmail", - onGeneratedMessageKey: "emailGenerated", - credentialTypeKey: "email", - copyKey: "copyEmail", - useGeneratedValueKey: "useThisEmail", - onlyOnRequest: false, - request: [], - engine: { - create( - dependencies: GeneratorDependencyProvider, - ): CredentialGenerator { - return new EmailRandomizer(dependencies.randomizer); - }, - }, - settings: { - initial: DefaultCatchallOptions, - constraints: { catchallDomain: { minLength: 1 } }, - account: { - key: "catchallGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "catchallType", - "catchallDomain", - ]), - state: GENERATOR_DISK, - initial: { - catchallType: "random", - catchallDomain: "", - }, - options: { - deserializer: (value) => value, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: {}, - combine(_acc: NoPolicy, _policy: Policy) { - return {}; - }, - createEvaluator(_policy: NoPolicy) { - return new DefaultPolicyEvaluator(); - }, - toConstraints(_policy: NoPolicy, email: string) { - return new CatchallConstraints(email); - }, - }, - }); - -const SUBADDRESS: CredentialGeneratorConfiguration = - Object.freeze({ - id: "subaddress", - category: "email", - nameKey: "plusAddressedEmail", - descriptionKey: "plusAddressedEmailDesc", - generateKey: "generateEmail", - onGeneratedMessageKey: "emailGenerated", - credentialTypeKey: "email", - copyKey: "copyEmail", - useGeneratedValueKey: "useThisEmail", - onlyOnRequest: false, - request: [], - engine: { - create( - dependencies: GeneratorDependencyProvider, - ): CredentialGenerator { - return new EmailRandomizer(dependencies.randomizer); - }, - }, - settings: { - initial: DefaultSubaddressOptions, - constraints: {}, - account: { - key: "subaddressGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "subaddressType", - "subaddressEmail", - ]), - state: GENERATOR_DISK, - initial: { - subaddressType: "random", - subaddressEmail: "", - }, - options: { - deserializer: (value) => value, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: {}, - combine(_acc: NoPolicy, _policy: Policy) { - return {}; - }, - createEvaluator(_policy: NoPolicy) { - return new DefaultPolicyEvaluator(); - }, - toConstraints(_policy: NoPolicy, email: string) { - return new SubaddressConstraints(email); - }, - }, - }); - -export function toCredentialGeneratorConfiguration( - configuration: ForwarderConfiguration, -) { - const forwarder = Object.freeze({ - id: { forwarder: configuration.id }, - category: "email", - nameKey: configuration.name, - descriptionKey: "forwardedEmailDesc", - generateKey: "generateEmail", - onGeneratedMessageKey: "emailGenerated", - credentialTypeKey: "email", - copyKey: "copyEmail", - useGeneratedValueKey: "useThisEmail", - onlyOnRequest: true, - request: configuration.forwarder.request, - engine: { - create(dependencies: GeneratorDependencyProvider) { - // FIXME: figure out why `configuration` fails to typecheck - const config: any = configuration; - return new Forwarder(config, dependencies.client, dependencies.i18nService); - }, - }, - settings: { - initial: configuration.forwarder.defaultSettings, - constraints: configuration.forwarder.settingsConstraints, - account: configuration.forwarder.local.settings, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: {}, - combine(_acc: NoPolicy, _policy: Policy) { - return {}; - }, - createEvaluator(_policy: NoPolicy) { - return new DefaultPolicyEvaluator(); - }, - toConstraints(_policy: NoPolicy) { - return new IdentityConstraint(); - }, - }, - } satisfies CredentialGeneratorConfiguration); - - return forwarder; -} - -/** Generator configurations */ -export const Generators = Object.freeze({ - /** Passphrase generator configuration */ - passphrase: PASSPHRASE, - - /** Password generator configuration */ - password: PASSWORD, - - /** Username generator configuration */ - username: USERNAME, - - /** Catchall email generator configuration */ - catchall: CATCHALL, - - /** Email subaddress generator configuration */ - subaddress: SUBADDRESS, -}); diff --git a/libs/tools/generator/core/src/data/index.ts b/libs/tools/generator/core/src/data/index.ts index 482703fd3c3..bcf57e98c9a 100644 --- a/libs/tools/generator/core/src/data/index.ts +++ b/libs/tools/generator/core/src/data/index.ts @@ -1,20 +1,8 @@ -export * from "./generators"; -export * from "./default-addy-io-options"; export * from "./default-catchall-options"; -export * from "./default-duck-duck-go-options"; -export * from "./default-fastmail-options"; -export * from "./default-forward-email-options"; export * from "./default-passphrase-boundaries"; export * from "./default-password-boundaries"; export * from "./default-eff-username-options"; -export * from "./default-firefox-relay-options"; export * from "./default-passphrase-generation-options"; export * from "./default-password-generation-options"; -export * from "./default-credential-preferences"; export * from "./default-subaddress-generator-options"; -export * from "./default-simple-login-options"; -export * from "./forwarders"; export * from "./integrations"; -export * from "./policies"; -export * from "./username-digits"; -export * from "./generator-types"; diff --git a/libs/tools/generator/core/src/data/policies.ts b/libs/tools/generator/core/src/data/policies.ts deleted file mode 100644 index 4e46718a395..00000000000 --- a/libs/tools/generator/core/src/data/policies.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - PassphraseGenerationOptions, - PassphraseGeneratorPolicy, - PasswordGenerationOptions, - PasswordGeneratorPolicy, - PolicyConfiguration, -} from "../types"; - -import { Generators } from "./generators"; - -/** Policy configurations - * @deprecated use Generator.*.policy instead - */ -export const Policies = Object.freeze({ - Passphrase: Generators.passphrase.policy, - Password: Generators.password.policy, -} satisfies { - /** Passphrase policy configuration */ - Passphrase: PolicyConfiguration; - - /** Password policy configuration */ - Password: PolicyConfiguration; -}); diff --git a/libs/tools/generator/core/src/data/username-digits.ts b/libs/tools/generator/core/src/data/username-digits.ts deleted file mode 100644 index 99ef15cf1ca..00000000000 --- a/libs/tools/generator/core/src/data/username-digits.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const UsernameDigits = Object.freeze({ - enabled: 4, - disabled: 0, -}); diff --git a/libs/tools/generator/core/src/engine/email-randomizer.spec.ts b/libs/tools/generator/core/src/engine/email-randomizer.spec.ts index fb953af1659..2ebe50d12d4 100644 --- a/libs/tools/generator/core/src/engine/email-randomizer.spec.ts +++ b/libs/tools/generator/core/src/engine/email-randomizer.spec.ts @@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended"; import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; +import { Algorithm, Type } from "../metadata"; + import { Randomizer } from "./abstractions"; import { EmailRandomizer } from "./email-randomizer"; @@ -41,7 +43,8 @@ describe("EmailRandomizer", () => { async (email) => { const emailRandomizer = new EmailRandomizer(randomizer); - const result = await emailRandomizer.randomAsciiSubaddress(email); + // this tests what happens when the type system is subverted + const result = await emailRandomizer.randomAsciiSubaddress(email!); expect(result).toEqual(""); }, @@ -100,7 +103,8 @@ describe("EmailRandomizer", () => { it.each([[null], [undefined], [""]])("returns null if the domain is %p", async (domain) => { const emailRandomizer = new EmailRandomizer(randomizer); - const result = await emailRandomizer.randomAsciiCatchall(domain); + // this tests what happens when the type system is subverted + const result = await emailRandomizer.randomAsciiCatchall(domain!); expect(result).toBeNull(); }); @@ -150,7 +154,8 @@ describe("EmailRandomizer", () => { it.each([[null], [undefined], [""]])("returns null if the domain is %p", async (domain) => { const emailRandomizer = new EmailRandomizer(randomizer); - const result = await emailRandomizer.randomWordsCatchall(domain); + // this tests what happens when the type system is subverted + const result = await emailRandomizer.randomWordsCatchall(domain!); expect(result).toBeNull(); }); @@ -214,32 +219,32 @@ describe("EmailRandomizer", () => { const email = new EmailRandomizer(randomizer); const result = await email.generate( - {}, + { algorithm: Algorithm.catchall }, { catchallDomain: "example.com", }, ); - expect(result.category).toEqual("catchall"); + expect(result.category).toEqual(Type.email); }); it("processes subaddress generation options", async () => { const email = new EmailRandomizer(randomizer); const result = await email.generate( - {}, + { algorithm: Algorithm.plusAddress }, { subaddressEmail: "foo@example.com", }, ); - expect(result.category).toEqual("subaddress"); + expect(result.category).toEqual(Type.email); }); it("throws when it cannot recognize the options type", async () => { const email = new EmailRandomizer(randomizer); - const result = email.generate({}, {}); + const result = email.generate({ algorithm: Algorithm.password }, {}); await expect(result).rejects.toBeInstanceOf(Error); }); diff --git a/libs/tools/generator/core/src/engine/email-randomizer.ts b/libs/tools/generator/core/src/engine/email-randomizer.ts index 0be95a975af..f673ba05fc0 100644 --- a/libs/tools/generator/core/src/engine/email-randomizer.ts +++ b/libs/tools/generator/core/src/engine/email-randomizer.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; +import { Type } from "../metadata"; import { CatchallGenerationOptions, CredentialGenerator, @@ -128,7 +129,7 @@ export class EmailRandomizer return new GeneratedCredential( email, - "catchall", + Type.email, Date.now(), request.source, request.website, @@ -138,7 +139,7 @@ export class EmailRandomizer return new GeneratedCredential( email, - "subaddress", + Type.email, Date.now(), request.source, request.website, diff --git a/libs/tools/generator/core/src/engine/forwarder.ts b/libs/tools/generator/core/src/engine/forwarder.ts index 6c6e574e873..5f41e35d21d 100644 --- a/libs/tools/generator/core/src/engine/forwarder.ts +++ b/libs/tools/generator/core/src/engine/forwarder.ts @@ -8,6 +8,7 @@ import { } from "@bitwarden/common/tools/integration/rpc"; import { GenerationRequest } from "@bitwarden/common/tools/types"; +import { Type } from "../metadata"; import { CredentialGenerator, GeneratedCredential } from "../types"; import { AccountRequest, ForwarderConfiguration } from "./forwarder-configuration"; @@ -40,9 +41,8 @@ export class Forwarder implements CredentialGenerator { const create = this.createForwardingAddress(this.configuration, settings); const result = await this.client.fetchJson(create, requestOptions); - const id = { forwarder: this.configuration.id }; - return new GeneratedCredential(result, id, Date.now()); + return new GeneratedCredential(result, Type.email, Date.now()); } private createContext( diff --git a/libs/tools/generator/core/src/engine/password-randomizer.spec.ts b/libs/tools/generator/core/src/engine/password-randomizer.spec.ts index fca98855fd5..a36c4bb5352 100644 --- a/libs/tools/generator/core/src/engine/password-randomizer.spec.ts +++ b/libs/tools/generator/core/src/engine/password-randomizer.spec.ts @@ -3,6 +3,7 @@ import { mock } from "jest-mock-extended"; import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; import { Randomizer } from "../abstractions"; +import { Algorithm, Type } from "../metadata"; import { Ascii } from "./data"; import { PasswordRandomizer } from "./password-randomizer"; @@ -341,32 +342,32 @@ describe("PasswordRandomizer", () => { const password = new PasswordRandomizer(randomizer); const result = await password.generate( - {}, + { algorithm: Algorithm.password }, { length: 10, }, ); - expect(result.category).toEqual("password"); + expect(result.category).toEqual(Type.password); }); it("processes passphrase generation options", async () => { const password = new PasswordRandomizer(randomizer); const result = await password.generate( - {}, + { algorithm: Algorithm.passphrase }, { numWords: 10, }, ); - expect(result.category).toEqual("passphrase"); + expect(result.category).toEqual(Type.password); }); it("throws when it cannot recognize the options type", async () => { const password = new PasswordRandomizer(randomizer); - const result = password.generate({}, {}); + const result = password.generate({ algorithm: Algorithm.username }, {}); await expect(result).rejects.toBeInstanceOf(Error); }); diff --git a/libs/tools/generator/core/src/engine/password-randomizer.ts b/libs/tools/generator/core/src/engine/password-randomizer.ts index a9612d2fb45..dc61ee064e1 100644 --- a/libs/tools/generator/core/src/engine/password-randomizer.ts +++ b/libs/tools/generator/core/src/engine/password-randomizer.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; +import { Type } from "../metadata"; import { CredentialGenerator, GenerateRequest, @@ -86,7 +87,7 @@ export class PasswordRandomizer return new GeneratedCredential( password, - "password", + Type.password, Date.now(), request.source, request.website, @@ -97,7 +98,7 @@ export class PasswordRandomizer return new GeneratedCredential( passphrase, - "passphrase", + Type.password, Date.now(), request.source, request.website, diff --git a/libs/tools/generator/core/src/engine/username-randomizer.spec.ts b/libs/tools/generator/core/src/engine/username-randomizer.spec.ts index 54d140e4469..be0650fe16e 100644 --- a/libs/tools/generator/core/src/engine/username-randomizer.spec.ts +++ b/libs/tools/generator/core/src/engine/username-randomizer.spec.ts @@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended"; import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; +import { Algorithm, Type } from "../metadata"; + import { Randomizer } from "./abstractions"; import { UsernameRandomizer } from "./username-randomizer"; @@ -108,19 +110,19 @@ describe("UsernameRandomizer", () => { const username = new UsernameRandomizer(randomizer); const result = await username.generate( - {}, + { algorithm: Algorithm.username }, { wordIncludeNumber: true, }, ); - expect(result.category).toEqual("username"); + expect(result.category).toEqual(Type.username); }); it("throws when it cannot recognize the options type", async () => { const username = new UsernameRandomizer(randomizer); - const result = username.generate({}, {}); + const result = username.generate({ algorithm: Algorithm.passphrase }, {}); await expect(result).rejects.toBeInstanceOf(Error); }); diff --git a/libs/tools/generator/core/src/index.ts b/libs/tools/generator/core/src/index.ts index 494d034b674..928e6786ff9 100644 --- a/libs/tools/generator/core/src/index.ts +++ b/libs/tools/generator/core/src/index.ts @@ -3,13 +3,34 @@ export * from "./abstractions"; export * from "./data"; export { createRandomizer } from "./factories"; export * from "./types"; -export { CredentialGeneratorService } from "./services"; +export { DefaultCredentialGeneratorService } from "./services"; +export { + CredentialType, + CredentialAlgorithm, + PasswordAlgorithm, + Algorithm, + BuiltIn, + Type, + Profile, + GeneratorMetadata, + GeneratorProfile, + AlgorithmMetadata, + AlgorithmsByType, +} from "./metadata"; +export { + isForwarderExtensionId, + isEmailAlgorithm, + isUsernameAlgorithm, + isPasswordAlgorithm, + isSameAlgorithm, +} from "./metadata/util"; // These internal interfacess are exposed for use by other generator modules // They are unstable and may change arbitrarily export * as engine from "./engine"; export * as integration from "./integration"; export * as policies from "./policies"; +export * as providers from "./providers"; export * as rx from "./rx"; export * as services from "./services"; export * as strategies from "./strategies"; diff --git a/libs/tools/generator/core/src/integration/addy-io.ts b/libs/tools/generator/core/src/integration/addy-io.ts index 93ffed3392a..bd1be0ee6ae 100644 --- a/libs/tools/generator/core/src/integration/addy-io.ts +++ b/libs/tools/generator/core/src/integration/addy-io.ts @@ -4,6 +4,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, @@ -101,7 +102,7 @@ const forwarder = Object.freeze({ export const AddyIo = Object.freeze({ // integration - id: "anonaddy" as IntegrationId & VendorId, + id: Vendor.addyio as IntegrationId & VendorId, name: "Addy.io", extends: ["forwarder"], diff --git a/libs/tools/generator/core/src/integration/duck-duck-go.ts b/libs/tools/generator/core/src/integration/duck-duck-go.ts index d2bd6173a14..b88a7f77a33 100644 --- a/libs/tools/generator/core/src/integration/duck-duck-go.ts +++ b/libs/tools/generator/core/src/integration/duck-duck-go.ts @@ -4,6 +4,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc"; import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier"; @@ -90,7 +91,7 @@ const forwarder = Object.freeze({ // integration-wide configuration export const DuckDuckGo = Object.freeze({ - id: "duckduckgo" as IntegrationId & VendorId, + id: Vendor.duckduckgo as IntegrationId & VendorId, name: "DuckDuckGo", baseUrl: "https://quack.duckduckgo.com/api", selfHost: "never", diff --git a/libs/tools/generator/core/src/integration/fastmail.ts b/libs/tools/generator/core/src/integration/fastmail.ts index bfde1aa70f5..a540807666e 100644 --- a/libs/tools/generator/core/src/integration/fastmail.ts +++ b/libs/tools/generator/core/src/integration/fastmail.ts @@ -6,6 +6,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc"; import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier"; @@ -160,7 +161,7 @@ const forwarder = Object.freeze({ // integration-wide configuration export const Fastmail = Object.freeze({ - id: "fastmail" as IntegrationId & VendorId, + id: Vendor.fastmail as IntegrationId & VendorId, name: "Fastmail", baseUrl: "https://api.fastmail.com", selfHost: "maybe", diff --git a/libs/tools/generator/core/src/integration/firefox-relay.ts b/libs/tools/generator/core/src/integration/firefox-relay.ts index f80de0c95dd..9fbd56aa6ed 100644 --- a/libs/tools/generator/core/src/integration/firefox-relay.ts +++ b/libs/tools/generator/core/src/integration/firefox-relay.ts @@ -4,6 +4,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc"; import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier"; @@ -98,7 +99,7 @@ const forwarder = Object.freeze({ // integration-wide configuration export const FirefoxRelay = Object.freeze({ - id: "firefoxrelay" as IntegrationId & VendorId, + id: Vendor.mozilla as IntegrationId & VendorId, name: "Firefox Relay", baseUrl: "https://relay.firefox.com/api", selfHost: "never", diff --git a/libs/tools/generator/core/src/integration/forward-email.ts b/libs/tools/generator/core/src/integration/forward-email.ts index 34b4602b94b..b53fc4ffab6 100644 --- a/libs/tools/generator/core/src/integration/forward-email.ts +++ b/libs/tools/generator/core/src/integration/forward-email.ts @@ -4,6 +4,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc"; import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier"; @@ -102,7 +103,7 @@ const forwarder = Object.freeze({ export const ForwardEmail = Object.freeze({ // integration metadata - id: "forwardemail" as IntegrationId & VendorId, + id: Vendor.forwardemail as IntegrationId & VendorId, name: "Forward Email", extends: ["forwarder"], diff --git a/libs/tools/generator/core/src/integration/simple-login.ts b/libs/tools/generator/core/src/integration/simple-login.ts index efbac69cec2..f3cc776d401 100644 --- a/libs/tools/generator/core/src/integration/simple-login.ts +++ b/libs/tools/generator/core/src/integration/simple-login.ts @@ -4,6 +4,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, @@ -104,7 +105,7 @@ const forwarder = Object.freeze({ // integration-wide configuration export const SimpleLogin = Object.freeze({ - id: "simplelogin" as IntegrationId & VendorId, + id: Vendor.simplelogin as IntegrationId & VendorId, name: "SimpleLogin", selfHost: "maybe", extends: ["forwarder"], diff --git a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts index c07deef5535..8bffa630dd9 100644 --- a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts +++ b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts @@ -1,6 +1,6 @@ -import { CredentialAlgorithm, CredentialType } from "./type"; +import { I18nKeyOrLiteral } from "@bitwarden/common/tools/types"; -type I18nKeyOrLiteral = string | { literal: string }; +import { CredentialAlgorithm, CredentialType } from "./type"; /** Credential generator metadata common across credential generators */ export type AlgorithmMetadata = { @@ -14,7 +14,7 @@ export type AlgorithmMetadata = { id: CredentialAlgorithm; /** The kind of credential generated by this configuration */ - category: CredentialType; + type: CredentialType; /** Used to order credential algorithms for display purposes. * Items with lesser weights appear before entries with greater @@ -23,6 +23,10 @@ export type AlgorithmMetadata = { weight: number; /** Localization keys */ + // FIXME: in practice, keys like `credentialGenerated` all align + // with credential types and contain duplicate keys. Extract + // them into a "credential type metadata" type and integrate + // that type with the algorithm metadata instead. i18nKeys: { /** descriptive name of the algorithm */ name: I18nKeyOrLiteral; diff --git a/libs/tools/generator/core/src/metadata/email/catchall.spec.ts b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts index d6cc1795e0b..1099a6d59ea 100644 --- a/libs/tools/generator/core/src/metadata/email/catchall.spec.ts +++ b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts @@ -2,7 +2,8 @@ import { mock } from "jest-mock-extended"; import { EmailRandomizer } from "../../engine"; import { CatchallConstraints } from "../../policies/catchall-constraints"; -import { CatchallGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CatchallGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/email/catchall.ts b/libs/tools/generator/core/src/metadata/email/catchall.ts index 0711e5c3719..991f6b18b73 100644 --- a/libs/tools/generator/core/src/metadata/email/catchall.ts +++ b/libs/tools/generator/core/src/metadata/email/catchall.ts @@ -4,17 +4,14 @@ import { deepFreeze } from "@bitwarden/common/tools/util"; import { EmailRandomizer } from "../../engine"; import { CatchallConstraints } from "../../policies/catchall-constraints"; -import { - CatchallGenerationOptions, - CredentialGenerator, - GeneratorDependencyProvider, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CatchallGenerationOptions, CredentialGenerator } from "../../types"; import { Algorithm, Type, Profile } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const catchall: GeneratorMetadata = deepFreeze({ id: Algorithm.catchall, - category: Type.email, + type: Type.email, weight: 210, i18nKeys: { name: "catchallEmail", diff --git a/libs/tools/generator/core/src/metadata/email/forwarder.ts b/libs/tools/generator/core/src/metadata/email/forwarder.ts index f4f150f33fa..1066f890ef8 100644 --- a/libs/tools/generator/core/src/metadata/email/forwarder.ts +++ b/libs/tools/generator/core/src/metadata/email/forwarder.ts @@ -1,19 +1,14 @@ import { ExtensionMetadata, ExtensionStorageKey } from "@bitwarden/common/tools/extension/type"; -import { SelfHostedApiSettings } from "@bitwarden/common/tools/integration/rpc"; import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; import { getForwarderConfiguration } from "../../data"; -import { EmailDomainSettings, EmailPrefixSettings } from "../../engine"; import { Forwarder } from "../../engine/forwarder"; -import { GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { ForwarderOptions } from "../../types"; import { Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; import { ForwarderProfileMetadata } from "../profile-metadata"; -// These options are used by all forwarders; each forwarder uses a different set, -// as defined by `GeneratorMetadata.capabilities.fields`. -type ForwarderOptions = Partial; - // update the extension metadata export function toForwarderMetadata( extension: ExtensionMetadata, @@ -28,7 +23,7 @@ export function toForwarderMetadata( const generator: GeneratorMetadata = { id: { forwarder: extension.product.vendor.id }, - category: Type.email, + type: Type.email, weight: 300, i18nKeys: { name, @@ -56,6 +51,12 @@ export function toForwarderMetadata( storage: { key: "forwarder", frame: 512, + initial: { + token: "", + baseUrl: "", + domain: "", + prefix: "", + }, options: { deserializer: (value) => value, clearOn: ["logout"], diff --git a/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts index 063cb71c23a..befc900ceab 100644 --- a/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts +++ b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts @@ -2,7 +2,8 @@ import { mock } from "jest-mock-extended"; import { EmailRandomizer } from "../../engine"; import { SubaddressConstraints } from "../../policies/subaddress-constraints"; -import { SubaddressGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { SubaddressGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/email/plus-address.ts b/libs/tools/generator/core/src/metadata/email/plus-address.ts index 0db0acd415c..940d6599442 100644 --- a/libs/tools/generator/core/src/metadata/email/plus-address.ts +++ b/libs/tools/generator/core/src/metadata/email/plus-address.ts @@ -4,17 +4,14 @@ import { deepFreeze } from "@bitwarden/common/tools/util"; import { EmailRandomizer } from "../../engine"; import { SubaddressConstraints } from "../../policies/subaddress-constraints"; -import { - CredentialGenerator, - GeneratorDependencyProvider, - SubaddressGenerationOptions, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, SubaddressGenerationOptions } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const plusAddress: GeneratorMetadata = deepFreeze({ id: Algorithm.plusAddress, - category: Type.email, + type: Type.email, weight: 200, i18nKeys: { name: "plusAddressedEmail", diff --git a/libs/tools/generator/core/src/metadata/generator-metadata.ts b/libs/tools/generator/core/src/metadata/generator-metadata.ts index 9296d30430e..704ce88b217 100644 --- a/libs/tools/generator/core/src/metadata/generator-metadata.ts +++ b/libs/tools/generator/core/src/metadata/generator-metadata.ts @@ -1,4 +1,5 @@ -import { CredentialGenerator, GeneratorDependencyProvider } from "../types"; +import { GeneratorDependencyProvider } from "../providers"; +import { CredentialGenerator } from "../types"; import { AlgorithmMetadata } from "./algorithm-metadata"; import { Profile } from "./data"; diff --git a/libs/tools/generator/core/src/metadata/index.ts b/libs/tools/generator/core/src/metadata/index.ts index d9437822270..17c02918705 100644 --- a/libs/tools/generator/core/src/metadata/index.ts +++ b/libs/tools/generator/core/src/metadata/index.ts @@ -3,7 +3,32 @@ import { AlgorithmsByType as AlgorithmsByTypeData, Type as TypeData, } from "./data"; +import catchall from "./email/catchall"; +import plusAddress from "./email/plus-address"; +import passphrase from "./password/eff-word-list"; +import password from "./password/random-password"; import { CredentialType, CredentialAlgorithm } from "./type"; +import effWordList from "./username/eff-word-list"; + +/** Credential generators hosted natively by the credential generator system. + * These are supplemented by generators from the {@link ExtensionService}. + */ +export const BuiltIn = Object.freeze({ + /** Catchall email address generator */ + catchall, + + /** plus-addressed email address generator */ + plusAddress, + + /** passphrase generator using the EFF word list */ + passphrase, + + /** password generator */ + password, + + /** username generator using the EFF word list */ + effWordList, +}); // `CredentialAlgorithm` is defined in terms of `ABT`; supplying // type information in the barrel file breaks a circular dependency. @@ -12,14 +37,29 @@ export const AlgorithmsByType: Record< CredentialType, ReadonlyArray > = AlgorithmsByTypeData; + +/** A list of all built-in algorithm identifiers + * @remarks this is useful when you need to filter invalid values + */ export const Algorithms: ReadonlyArray = Object.freeze( Object.values(AlgorithmData), ); + +/** A list of all built-in algorithm types + * @remarks this is useful when you need to filter invalid values + */ export const Types: ReadonlyArray = Object.freeze(Object.values(TypeData)); export { Profile, Type, Algorithm } from "./data"; export { toForwarderMetadata } from "./email/forwarder"; +export { AlgorithmMetadata } from "./algorithm-metadata"; export { GeneratorMetadata } from "./generator-metadata"; export { ProfileContext, CoreProfileMetadata, ProfileMetadata } from "./profile-metadata"; -export { GeneratorProfile, CredentialAlgorithm, CredentialType } from "./type"; +export { + GeneratorProfile, + CredentialAlgorithm, + PasswordAlgorithm, + CredentialType, + ForwarderExtensionId, +} from "./type"; export { isForwarderProfile, toVendorId, isForwarderExtensionId } from "./util"; diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts index e02d63d3d59..0c0693af272 100644 --- a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts @@ -5,7 +5,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PasswordRandomizer } from "../../engine"; import { PassphrasePolicyConstraints } from "../../policies"; -import { PassphraseGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { PassphraseGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts index fc86032bf6b..021112f7c89 100644 --- a/libs/tools/generator/core/src/metadata/password/eff-word-list.ts +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts @@ -5,17 +5,14 @@ import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; import { PasswordRandomizer } from "../../engine"; import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies"; -import { - CredentialGenerator, - GeneratorDependencyProvider, - PassphraseGenerationOptions, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, PassphraseGenerationOptions } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const passphrase: GeneratorMetadata = { id: Algorithm.passphrase, - category: Type.password, + type: Type.password, weight: 110, i18nKeys: { name: "passphrase", @@ -26,7 +23,7 @@ const passphrase: GeneratorMetadata = { useCredential: "useThisPassphrase", }, capabilities: { - autogenerate: false, + autogenerate: true, fields: [], }, engine: { diff --git a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts index 9e38c50ee2a..b22f3e9356d 100644 --- a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts +++ b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts @@ -5,7 +5,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PasswordRandomizer } from "../../engine"; import { DynamicPasswordPolicyConstraints } from "../../policies"; -import { PasswordGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { PasswordGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/password/random-password.ts b/libs/tools/generator/core/src/metadata/password/random-password.ts index 693236b0967..e446f1962a5 100644 --- a/libs/tools/generator/core/src/metadata/password/random-password.ts +++ b/libs/tools/generator/core/src/metadata/password/random-password.ts @@ -5,17 +5,14 @@ import { deepFreeze } from "@bitwarden/common/tools/util"; import { PasswordRandomizer } from "../../engine"; import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies"; -import { - CredentialGenerator, - GeneratorDependencyProvider, - PasswordGeneratorSettings, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, PasswordGeneratorSettings } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const password: GeneratorMetadata = deepFreeze({ id: Algorithm.password, - category: Type.password, + type: Type.password, weight: 100, i18nKeys: { name: "password", diff --git a/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts index d47d5ec9fcb..beebb016504 100644 --- a/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts +++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts @@ -3,7 +3,8 @@ import { mock } from "jest-mock-extended"; import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; import { UsernameRandomizer } from "../../engine"; -import { EffUsernameGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { EffUsernameGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/username/eff-word-list.ts b/libs/tools/generator/core/src/metadata/username/eff-word-list.ts index 6373daf8ed5..2802eea2c08 100644 --- a/libs/tools/generator/core/src/metadata/username/eff-word-list.ts +++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.ts @@ -4,17 +4,14 @@ import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state import { deepFreeze } from "@bitwarden/common/tools/util"; import { UsernameRandomizer } from "../../engine"; -import { - CredentialGenerator, - EffUsernameGenerationOptions, - GeneratorDependencyProvider, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, EffUsernameGenerationOptions } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const effWordList: GeneratorMetadata = deepFreeze({ id: Algorithm.username, - category: Type.username, + type: Type.username, weight: 400, i18nKeys: { name: "randomWord", diff --git a/libs/tools/generator/core/src/metadata/util.ts b/libs/tools/generator/core/src/metadata/util.ts index 86b2742e86d..a8e8879b57c 100644 --- a/libs/tools/generator/core/src/metadata/util.ts +++ b/libs/tools/generator/core/src/metadata/util.ts @@ -12,23 +12,23 @@ import { /** Returns true when the input algorithm is a password algorithm. */ export function isPasswordAlgorithm( - algorithm: CredentialAlgorithm, + algorithm: CredentialAlgorithm | null, ): algorithm is PasswordAlgorithm { return AlgorithmsByType.password.includes(algorithm as any); } /** Returns true when the input algorithm is a username algorithm. */ export function isUsernameAlgorithm( - algorithm: CredentialAlgorithm, + algorithm: CredentialAlgorithm | null, ): algorithm is UsernameAlgorithm { return AlgorithmsByType.username.includes(algorithm as any); } /** Returns true when the input algorithm is a forwarder integration. */ export function isForwarderExtensionId( - algorithm: CredentialAlgorithm, + algorithm: CredentialAlgorithm | null, ): algorithm is ForwarderExtensionId { - return algorithm && typeof algorithm === "object" && "forwarder" in algorithm; + return !!(algorithm && typeof algorithm === "object" && "forwarder" in algorithm); } /** Extract a `VendorId` from a `CredentialAlgorithm`. diff --git a/libs/tools/generator/core/src/policies/available-algorithms-constraint.ts b/libs/tools/generator/core/src/policies/available-algorithms-constraint.ts new file mode 100644 index 00000000000..1824581664b --- /dev/null +++ b/libs/tools/generator/core/src/policies/available-algorithms-constraint.ts @@ -0,0 +1,76 @@ +import { SemanticLogger } from "@bitwarden/common/tools/log"; +import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider"; +import { Constraints, StateConstraints } from "@bitwarden/common/tools/types"; + +import { CredentialAlgorithm, CredentialType } from "../metadata"; +import { CredentialPreference } from "../types"; +import { TypeRequest } from "../types/metadata-request"; + +export class AvailableAlgorithmsConstraint implements StateConstraints { + /** Well-known constraints of `State` */ + readonly constraints: Readonly> = {}; + + /** Creates a password policy constraints + * @param algorithms loads the algorithms for an algorithm type + * @param isAvailable returns `true` when `algorithm` is enabled by policy + * @param system provides logging facilities + */ + constructor( + readonly algorithms: (request: TypeRequest) => CredentialAlgorithm[], + readonly isAvailable: (algorithm: CredentialAlgorithm) => boolean, + readonly system: UserStateSubjectDependencyProvider, + ) { + this.log = system.log({ type: "AvailableAlgorithmsConstraint" }); + } + private readonly log: SemanticLogger; + + adjust(preferences: CredentialPreference): CredentialPreference { + const result: any = {}; + + const types = Object.keys(preferences) as CredentialType[]; + for (const t of types) { + result[t] = this.adjustPreference(t, preferences[t]); + } + + return result; + } + + private adjustPreference(type: CredentialType, preference: { algorithm: CredentialAlgorithm }) { + if (this.isAvailable(preference.algorithm)) { + this.log.debug({ preference, type }, "using preferred algorithm"); + + return preference; + } + + // choose a default - this algorithm is arbitrary, but stable. + const algorithms = type ? this.algorithms({ type: type }) : []; + const defaultAlgorithm = algorithms.find(this.isAvailable) ?? null; + + // adjust the preference + let adjustedPreference; + if (defaultAlgorithm) { + adjustedPreference = { + ...preference, + algorithm: defaultAlgorithm, + updated: this.system.now(), + }; + this.log.debug( + { preference, defaultAlgorithm, type }, + "preference not available; defaulting the algorithm", + ); + } else { + // FIXME: hard-code a fallback in category metadata + this.log.warn( + { preference, type }, + "preference not available and default algorithm not found; continuing with preference", + ); + adjustedPreference = preference; + } + + return adjustedPreference; + } + + fix(preferences: CredentialPreference): CredentialPreference { + return preferences; + } +} diff --git a/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts b/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts index 1ef0adc1af4..5f699974fba 100644 --- a/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts +++ b/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts @@ -2,15 +2,15 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PolicyId } from "@bitwarden/common/types/guid"; -import { CredentialAlgorithms, PasswordAlgorithms } from "../data"; +import { Algorithm, Algorithms, AlgorithmsByType } from "../metadata"; import { availableAlgorithms } from "./available-algorithms-policy"; -describe("availableAlgorithmsPolicy", () => { +describe("availableAlgorithms_vNextPolicy", () => { it("returns all algorithms", () => { const result = availableAlgorithms([]); - for (const expected of CredentialAlgorithms) { + for (const expected of Algorithms) { expect(result).toContain(expected); } }); @@ -30,7 +30,7 @@ describe("availableAlgorithmsPolicy", () => { expect(result).toContain(override); - for (const expected of PasswordAlgorithms.filter((a) => a !== override)) { + for (const expected of AlgorithmsByType[Algorithm.password].filter((a) => a !== override)) { expect(result).not.toContain(expected); } }); @@ -50,7 +50,7 @@ describe("availableAlgorithmsPolicy", () => { expect(result).toContain(override); - for (const expected of PasswordAlgorithms.filter((a) => a !== override)) { + for (const expected of AlgorithmsByType[Algorithm.password].filter((a) => a !== override)) { expect(result).not.toContain(expected); } }); @@ -79,7 +79,7 @@ describe("availableAlgorithmsPolicy", () => { expect(result).toContain("password"); - for (const expected of PasswordAlgorithms.filter((a) => a !== "password")) { + for (const expected of AlgorithmsByType[Algorithm.password].filter((a) => a !== "password")) { expect(result).not.toContain(expected); } }); @@ -97,7 +97,7 @@ describe("availableAlgorithmsPolicy", () => { const result = availableAlgorithms([policy]); - for (const expected of CredentialAlgorithms) { + for (const expected of Algorithms) { expect(result).toContain(expected); } }); @@ -115,7 +115,7 @@ describe("availableAlgorithmsPolicy", () => { const result = availableAlgorithms([policy]); - for (const expected of CredentialAlgorithms) { + for (const expected of Algorithms) { expect(result).toContain(expected); } }); @@ -133,7 +133,7 @@ describe("availableAlgorithmsPolicy", () => { const result = availableAlgorithms([policy]); - for (const expected of CredentialAlgorithms) { + for (const expected of Algorithms) { expect(result).toContain(expected); } }); diff --git a/libs/tools/generator/core/src/policies/available-algorithms-policy.ts b/libs/tools/generator/core/src/policies/available-algorithms-policy.ts index 0c44a1a0408..e63b648cf44 100644 --- a/libs/tools/generator/core/src/policies/available-algorithms-policy.ts +++ b/libs/tools/generator/core/src/policies/available-algorithms-policy.ts @@ -1,57 +1,30 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { PolicyType } from "@bitwarden/common/admin-console/enums"; // FIXME: use index.ts imports once policy abstractions and models // implement ADR-0002 import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { - CredentialAlgorithm as LegacyAlgorithm, - EmailAlgorithms, - PasswordAlgorithms, - UsernameAlgorithms, -} from ".."; -import { CredentialAlgorithm } from "../metadata"; +import { AlgorithmsByType, CredentialAlgorithm, Type } from "../metadata"; /** Reduces policies to a set of available algorithms * @param policies the policies to reduce * @returns the resulting `AlgorithmAvailabilityPolicy` */ -export function availableAlgorithms(policies: Policy[]): LegacyAlgorithm[] { +export function availableAlgorithms(policies: Policy[]): CredentialAlgorithm[] { const overridePassword = policies .filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled) .reduce( (type, policy) => (type === "password" ? type : (policy.data.overridePasswordType ?? type)), - null as LegacyAlgorithm, + null as CredentialAlgorithm | null, ); - const policy: LegacyAlgorithm[] = [...EmailAlgorithms, ...UsernameAlgorithms]; + const policy: CredentialAlgorithm[] = [ + ...AlgorithmsByType[Type.email], + ...AlgorithmsByType[Type.username], + ]; if (overridePassword) { policy.push(overridePassword); } else { - policy.push(...PasswordAlgorithms); - } - - return policy; -} - -/** Reduces policies to a set of available algorithms - * @param policies the policies to reduce - * @returns the resulting `AlgorithmAvailabilityPolicy` - */ -export function availableAlgorithms_vNext(policies: Policy[]): CredentialAlgorithm[] { - const overridePassword = policies - .filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled) - .reduce( - (type, policy) => (type === "password" ? type : (policy.data.overridePasswordType ?? type)), - null as CredentialAlgorithm, - ); - - const policy: CredentialAlgorithm[] = [...EmailAlgorithms, ...UsernameAlgorithms]; - if (overridePassword) { - policy.push(overridePassword); - } else { - policy.push(...PasswordAlgorithms); + policy.push(...AlgorithmsByType[Type.password]); } return policy; diff --git a/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.spec.ts b/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.spec.ts index c8ae02ef723..0bebb0825bf 100644 --- a/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.spec.ts +++ b/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.spec.ts @@ -1,15 +1,25 @@ import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; -import { Generators } from "../data"; +import { BuiltIn, Profile } from "../metadata"; import { PasswordGeneratorSettings } from "../types"; import { AtLeastOne, Zero } from "./constraints"; import { DynamicPasswordPolicyConstraints } from "./dynamic-password-policy-constraints"; -const accoutSettings = Generators.password.settings.account as ObjectKey; -const defaultOptions = accoutSettings.initial; -const disabledPolicy = Generators.password.policy.disabledValue; -const someConstraints = Generators.password.settings.constraints; +// non-null assertions used because these are always-defined constants +const accoutSettings = BuiltIn.password.profiles[Profile.account]! + .storage as ObjectKey; +const defaultOptions = accoutSettings.initial!; +const disabledPolicy = { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, +}; +const someConstraints = BuiltIn.password.profiles[Profile.account]!.constraints.default; describe("DynamicPasswordPolicyConstraints", () => { describe("constructor", () => { @@ -33,8 +43,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.lowercase.readonly).toEqual(true); - expect(constraints.lowercase.requiredValue).toEqual(true); + expect(constraints.lowercase?.readonly).toEqual(true); + expect(constraints.lowercase?.requiredValue).toEqual(true); expect(constraints.minLowercase).toEqual({ min: 1 }); }); @@ -43,8 +53,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.uppercase.readonly).toEqual(true); - expect(constraints.uppercase.requiredValue).toEqual(true); + expect(constraints.uppercase?.readonly).toEqual(true); + expect(constraints.uppercase?.requiredValue).toEqual(true); expect(constraints.minUppercase).toEqual({ min: 1 }); }); @@ -53,8 +63,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.number.readonly).toEqual(true); - expect(constraints.number.requiredValue).toEqual(true); + expect(constraints.number?.readonly).toEqual(true); + expect(constraints.number?.requiredValue).toEqual(true); expect(constraints.minNumber).toEqual({ min: 1, max: 9 }); }); @@ -63,8 +73,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.special.readonly).toEqual(true); - expect(constraints.special.requiredValue).toEqual(true); + expect(constraints.special?.readonly).toEqual(true); + expect(constraints.special?.requiredValue).toEqual(true); expect(constraints.minSpecial).toEqual({ min: 1, max: 9 }); }); @@ -73,8 +83,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.number.readonly).toEqual(true); - expect(constraints.number.requiredValue).toEqual(true); + expect(constraints.number?.readonly).toEqual(true); + expect(constraints.number?.requiredValue).toEqual(true); expect(constraints.minNumber).toEqual({ min: 2, max: 9 }); }); @@ -83,8 +93,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.special.readonly).toEqual(true); - expect(constraints.special.requiredValue).toEqual(true); + expect(constraints.special?.readonly).toEqual(true); + expect(constraints.special?.requiredValue).toEqual(true); expect(constraints.minSpecial).toEqual({ min: 2, max: 9 }); }); @@ -140,7 +150,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const dynamic = new DynamicPasswordPolicyConstraints( { ...disabledPolicy, - useLowercase, + // the `undefined` case is testing behavior when the type system is bypassed + useLowercase: useLowercase!, }, someConstraints, ); @@ -185,7 +196,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const dynamic = new DynamicPasswordPolicyConstraints( { ...disabledPolicy, - useUppercase, + // the `undefined` case is testing behavior when the type system is bypassed + useUppercase: useUppercase!, }, someConstraints, ); diff --git a/libs/tools/generator/core/src/policies/index.ts b/libs/tools/generator/core/src/policies/index.ts index 0d05e702306..893f0402d38 100644 --- a/libs/tools/generator/core/src/policies/index.ts +++ b/libs/tools/generator/core/src/policies/index.ts @@ -5,3 +5,5 @@ export { PassphrasePolicyConstraints } from "./passphrase-policy-constraints"; export { PasswordGeneratorOptionsEvaluator } from "./password-generator-options-evaluator"; export { passphraseLeastPrivilege } from "./passphrase-least-privilege"; export { passwordLeastPrivilege } from "./password-least-privilege"; +export { AvailableAlgorithmsConstraint } from "./available-algorithms-constraint"; +export { availableAlgorithms } from "./available-algorithms-policy"; diff --git a/libs/tools/generator/core/src/policies/passphrase-generator-options-evaluator.spec.ts b/libs/tools/generator/core/src/policies/passphrase-generator-options-evaluator.spec.ts index 3b1eb799391..5fcf847c504 100644 --- a/libs/tools/generator/core/src/policies/passphrase-generator-options-evaluator.spec.ts +++ b/libs/tools/generator/core/src/policies/passphrase-generator-options-evaluator.spec.ts @@ -1,12 +1,32 @@ -import { Policies, DefaultPassphraseBoundaries } from "../data"; -import { PassphraseGenerationOptions } from "../types"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { DefaultPassphraseBoundaries } from "../data"; +import { + PassphraseGenerationOptions, + PassphraseGeneratorPolicy, + PolicyConfiguration, +} from "../types"; import { PassphraseGeneratorOptionsEvaluator } from "./passphrase-generator-options-evaluator"; +import { passphraseLeastPrivilege } from "./passphrase-least-privilege"; -describe("Password generator options builder", () => { +const Passphrase: PolicyConfiguration = + deepFreeze({ + type: PolicyType.PasswordGenerator, + disabledValue: { + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }, + combine: passphraseLeastPrivilege, + createEvaluator: (policy) => new PassphraseGeneratorOptionsEvaluator(policy), + }); + +describe("Passphrase generator options builder", () => { describe("constructor()", () => { it("should set the policy object to a copy of the input policy", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.minNumberWords = 10; // arbitrary change for deep equality check const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -16,7 +36,7 @@ describe("Password generator options builder", () => { }); it("should set default boundaries when a default policy is used", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); expect(builder.numWords).toEqual(DefaultPassphraseBoundaries.numWords); @@ -25,7 +45,7 @@ describe("Password generator options builder", () => { it.each([1, 2])( "should use the default word boundaries when they are greater than `policy.minNumberWords` (= %i)", (minNumberWords) => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.minNumberWords = minNumberWords; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -37,7 +57,7 @@ describe("Password generator options builder", () => { it.each([8, 12, 18])( "should use `policy.minNumberWords` (= %i) when it is greater than the default minimum words", (minNumberWords) => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.minNumberWords = minNumberWords; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -50,7 +70,7 @@ describe("Password generator options builder", () => { it.each([150, 300, 9000])( "should use `policy.minNumberWords` (= %i) when it is greater than the default boundaries", (minNumberWords) => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.minNumberWords = minNumberWords; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -63,14 +83,14 @@ describe("Password generator options builder", () => { describe("policyInEffect", () => { it("should return false when the policy has no effect", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); expect(builder.policyInEffect).toEqual(false); }); it("should return true when the policy has a numWords greater than the default boundary", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.minNumberWords = DefaultPassphraseBoundaries.numWords.min + 1; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -78,7 +98,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has capitalize enabled", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.capitalize = true; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -86,7 +106,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has includeNumber enabled", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.includeNumber = true; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -98,7 +118,7 @@ describe("Password generator options builder", () => { // All tests should freeze the options to ensure they are not modified it("should set `capitalize` to `false` when the policy does not override it", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({}); @@ -108,7 +128,7 @@ describe("Password generator options builder", () => { }); it("should set `capitalize` to `true` when the policy overrides it", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.capitalize = true; const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ capitalize: false }); @@ -119,7 +139,7 @@ describe("Password generator options builder", () => { }); it("should set `includeNumber` to false when the policy does not override it", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({}); @@ -129,7 +149,7 @@ describe("Password generator options builder", () => { }); it("should set `includeNumber` to true when the policy overrides it", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.includeNumber = true; const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ includeNumber: false }); @@ -140,7 +160,7 @@ describe("Password generator options builder", () => { }); it("should set `numWords` to the minimum value when it isn't supplied", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({}); @@ -154,7 +174,7 @@ describe("Password generator options builder", () => { (numWords) => { expect(numWords).toBeLessThan(DefaultPassphraseBoundaries.numWords.min); - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ numWords }); @@ -170,7 +190,7 @@ describe("Password generator options builder", () => { expect(numWords).toBeGreaterThanOrEqual(DefaultPassphraseBoundaries.numWords.min); expect(numWords).toBeLessThanOrEqual(DefaultPassphraseBoundaries.numWords.max); - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ numWords }); @@ -185,7 +205,7 @@ describe("Password generator options builder", () => { (numWords) => { expect(numWords).toBeGreaterThan(DefaultPassphraseBoundaries.numWords.max); - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ numWords }); @@ -196,7 +216,7 @@ describe("Password generator options builder", () => { ); it("should preserve unknown properties", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ unknown: "property", @@ -214,7 +234,7 @@ describe("Password generator options builder", () => { // All tests should freeze the options to ensure they are not modified it("should return the input options without altering them", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ wordSeparator: "%" }); @@ -224,7 +244,7 @@ describe("Password generator options builder", () => { }); it("should set `wordSeparator` to '-' when it isn't supplied and there is no policy override", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({}); @@ -234,7 +254,7 @@ describe("Password generator options builder", () => { }); it("should leave `wordSeparator` as the empty string '' when it is the empty string", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ wordSeparator: "" }); @@ -244,7 +264,7 @@ describe("Password generator options builder", () => { }); it("should preserve unknown properties", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ unknown: "property", diff --git a/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts b/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts index ecac3855987..0fbc1796e9e 100644 --- a/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts +++ b/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts @@ -4,8 +4,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PolicyId } from "@bitwarden/common/types/guid"; -import { Policies } from "../data"; - import { passphraseLeastPrivilege } from "./passphrase-least-privilege"; function createPolicy( @@ -22,21 +20,27 @@ function createPolicy( }); } +const disabledValue = Object.freeze({ + minNumberWords: 0, + capitalize: false, + includeNumber: false, +}); + describe("passphraseLeastPrivilege", () => { it("should return the accumulator when the policy type does not apply", () => { const policy = createPolicy({}, PolicyType.RequireSso); - const result = passphraseLeastPrivilege(Policies.Passphrase.disabledValue, policy); + const result = passphraseLeastPrivilege(disabledValue, policy); - expect(result).toEqual(Policies.Passphrase.disabledValue); + expect(result).toEqual(disabledValue); }); it("should return the accumulator when the policy is not enabled", () => { const policy = createPolicy({}, PolicyType.PasswordGenerator, false); - const result = passphraseLeastPrivilege(Policies.Passphrase.disabledValue, policy); + const result = passphraseLeastPrivilege(disabledValue, policy); - expect(result).toEqual(Policies.Passphrase.disabledValue); + expect(result).toEqual(disabledValue); }); it.each([ @@ -46,8 +50,8 @@ describe("passphraseLeastPrivilege", () => { ])("should take the %p from the policy", (input, value) => { const policy = createPolicy({ [input]: value }); - const result = passphraseLeastPrivilege(Policies.Passphrase.disabledValue, policy); + const result = passphraseLeastPrivilege(disabledValue, policy); - expect(result).toEqual({ ...Policies.Passphrase.disabledValue, [input]: value }); + expect(result).toEqual({ ...disabledValue, [input]: value }); }); }); diff --git a/libs/tools/generator/core/src/policies/passphrase-policy-constraints.spec.ts b/libs/tools/generator/core/src/policies/passphrase-policy-constraints.spec.ts index d6e0a5615dc..6306382c84e 100644 --- a/libs/tools/generator/core/src/policies/passphrase-policy-constraints.spec.ts +++ b/libs/tools/generator/core/src/policies/passphrase-policy-constraints.spec.ts @@ -1,4 +1,4 @@ -import { Generators } from "../data"; +import { BuiltIn, Profile } from "../metadata"; import { PassphrasePolicyConstraints } from "./passphrase-policy-constraints"; @@ -9,8 +9,12 @@ const SomeSettings = { wordSeparator: "-", }; -const disabledPolicy = Generators.passphrase.policy.disabledValue; -const someConstraints = Generators.passphrase.settings.constraints; +const disabledPolicy = { + minNumberWords: 0, + capitalize: false, + includeNumber: false, +}; +const someConstraints = BuiltIn.passphrase.profiles[Profile.account]!.constraints.default; describe("PassphrasePolicyConstraints", () => { describe("constructor", () => { @@ -61,7 +65,7 @@ describe("PassphrasePolicyConstraints", () => { expect(constraints.policyInEffect).toBeTruthy(); expect(constraints.numWords).toMatchObject({ min: 10, - max: someConstraints.numWords.max, + max: someConstraints.numWords?.max, }); }); }); @@ -84,8 +88,8 @@ describe("PassphrasePolicyConstraints", () => { }); it.each([ - [1, someConstraints.numWords.min, 3, someConstraints.numWords.max], - [21, someConstraints.numWords.min, 20, someConstraints.numWords.max], + [1, someConstraints.numWords?.min, 3, someConstraints.numWords?.max], + [21, someConstraints.numWords?.min, 20, someConstraints.numWords?.max], ])( `fits numWords (=%p) within the default bounds (%p <= %p <= %p)`, (value, _, expected, __) => { @@ -98,8 +102,8 @@ describe("PassphrasePolicyConstraints", () => { ); it.each([ - [1, 6, 6, someConstraints.numWords.max], - [21, 20, 20, someConstraints.numWords.max], + [1, 6, 6, someConstraints.numWords?.max], + [21, 20, 20, someConstraints.numWords?.max], ])( "fits numWords (=%p) within the policy bounds (%p <= %p <= %p)", (value, minNumberWords, expected, _) => { diff --git a/libs/tools/generator/core/src/policies/password-generator-options-evaluator.spec.ts b/libs/tools/generator/core/src/policies/password-generator-options-evaluator.spec.ts index 91334f91f85..a088f93d3fe 100644 --- a/libs/tools/generator/core/src/policies/password-generator-options-evaluator.spec.ts +++ b/libs/tools/generator/core/src/policies/password-generator-options-evaluator.spec.ts @@ -1,14 +1,34 @@ -import { DefaultPasswordBoundaries, Policies } from "../data"; -import { PasswordGenerationOptions } from "../types"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { DefaultPasswordBoundaries } from "../data"; +import { PasswordGenerationOptions, PasswordGeneratorPolicy, PolicyConfiguration } from "../types"; import { PasswordGeneratorOptionsEvaluator } from "./password-generator-options-evaluator"; +import { passwordLeastPrivilege } from "./password-least-privilege"; + +const Password: PolicyConfiguration = + deepFreeze({ + type: PolicyType.PasswordGenerator, + disabledValue: { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }, + combine: passwordLeastPrivilege, + createEvaluator: (policy) => new PasswordGeneratorOptionsEvaluator(policy), + }); describe("Password generator options builder", () => { const defaultOptions = Object.freeze({ minLength: 0 }); describe("constructor()", () => { it("should set the policy object to a copy of the input policy", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.minLength = 10; // arbitrary change for deep equality check const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -18,7 +38,7 @@ describe("Password generator options builder", () => { }); it("should set default boundaries when a default policy is used", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -32,7 +52,7 @@ describe("Password generator options builder", () => { (minLength) => { expect(minLength).toBeLessThan(DefaultPasswordBoundaries.length.min); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.minLength = minLength; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -47,7 +67,7 @@ describe("Password generator options builder", () => { expect(expectedLength).toBeGreaterThan(DefaultPasswordBoundaries.length.min); expect(expectedLength).toBeLessThanOrEqual(DefaultPasswordBoundaries.length.max); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.minLength = expectedLength; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -62,7 +82,7 @@ describe("Password generator options builder", () => { (expectedLength) => { expect(expectedLength).toBeGreaterThan(DefaultPasswordBoundaries.length.max); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.minLength = expectedLength; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -78,7 +98,7 @@ describe("Password generator options builder", () => { expect(expectedMinDigits).toBeGreaterThan(DefaultPasswordBoundaries.minDigits.min); expect(expectedMinDigits).toBeLessThanOrEqual(DefaultPasswordBoundaries.minDigits.max); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.numberCount = expectedMinDigits; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -93,7 +113,7 @@ describe("Password generator options builder", () => { (expectedMinDigits) => { expect(expectedMinDigits).toBeGreaterThan(DefaultPasswordBoundaries.minDigits.max); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.numberCount = expectedMinDigits; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -113,7 +133,7 @@ describe("Password generator options builder", () => { DefaultPasswordBoundaries.minSpecialCharacters.max, ); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.specialCount = expectedSpecialCharacters; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -132,7 +152,7 @@ describe("Password generator options builder", () => { DefaultPasswordBoundaries.minSpecialCharacters.max, ); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.specialCount = expectedSpecialCharacters; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -151,7 +171,7 @@ describe("Password generator options builder", () => { (expectedLength, numberCount, specialCount) => { expect(expectedLength).toBeGreaterThanOrEqual(DefaultPasswordBoundaries.length.min); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.numberCount = numberCount; policy.specialCount = specialCount; @@ -164,14 +184,14 @@ describe("Password generator options builder", () => { describe("policyInEffect", () => { it("should return false when the policy has no effect", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(builder.policyInEffect).toEqual(false); }); it("should return true when the policy has a minlength greater than the default boundary", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.minLength = DefaultPasswordBoundaries.length.min + 1; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -179,7 +199,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has a number count greater than the default boundary", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.numberCount = DefaultPasswordBoundaries.minDigits.min + 1; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -187,7 +207,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has a special character count greater than the default boundary", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.specialCount = DefaultPasswordBoundaries.minSpecialCharacters.min + 1; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -195,7 +215,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has uppercase enabled", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useUppercase = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -203,7 +223,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has lowercase enabled", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useLowercase = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -211,7 +231,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has numbers enabled", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useNumbers = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -219,7 +239,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has special characters enabled", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useSpecial = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -237,7 +257,7 @@ describe("Password generator options builder", () => { ])( "should set `options.uppercase` to '%s' when `policy.useUppercase` is false and `options.uppercase` is '%s'", (expectedUppercase, uppercase) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useUppercase = false; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, uppercase }); @@ -251,7 +271,7 @@ describe("Password generator options builder", () => { it.each([false, true, undefined])( "should set `options.uppercase` (= %s) to true when `policy.useUppercase` is true", (uppercase) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useUppercase = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, uppercase }); @@ -269,7 +289,7 @@ describe("Password generator options builder", () => { ])( "should set `options.lowercase` to '%s' when `policy.useLowercase` is false and `options.lowercase` is '%s'", (expectedLowercase, lowercase) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useLowercase = false; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, lowercase }); @@ -283,7 +303,7 @@ describe("Password generator options builder", () => { it.each([false, true, undefined])( "should set `options.lowercase` (= %s) to true when `policy.useLowercase` is true", (lowercase) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useLowercase = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, lowercase }); @@ -301,7 +321,7 @@ describe("Password generator options builder", () => { ])( "should set `options.number` to '%s' when `policy.useNumbers` is false and `options.number` is '%s'", (expectedNumber, number) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useNumbers = false; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, number }); @@ -315,7 +335,7 @@ describe("Password generator options builder", () => { it.each([false, true, undefined])( "should set `options.number` (= %s) to true when `policy.useNumbers` is true", (number) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useNumbers = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, number }); @@ -333,7 +353,7 @@ describe("Password generator options builder", () => { ])( "should set `options.special` to '%s' when `policy.useSpecial` is false and `options.special` is '%s'", (expectedSpecial, special) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useSpecial = false; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, special }); @@ -347,7 +367,7 @@ describe("Password generator options builder", () => { it.each([false, true, undefined])( "should set `options.special` (= %s) to true when `policy.useSpecial` is true", (special) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useSpecial = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, special }); @@ -361,7 +381,7 @@ describe("Password generator options builder", () => { it.each([1, 2, 3, 4])( "should set `options.length` (= %i) to the minimum it is less than the minimum length", (length) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(length).toBeLessThan(builder.length.min); @@ -376,7 +396,7 @@ describe("Password generator options builder", () => { it.each([5, 10, 50, 100, 128])( "should not change `options.length` (= %i) when it is within the boundaries", (length) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(length).toBeGreaterThanOrEqual(builder.length.min); expect(length).toBeLessThanOrEqual(builder.length.max); @@ -392,7 +412,7 @@ describe("Password generator options builder", () => { it.each([129, 500, 9000])( "should set `options.length` (= %i) to the maximum length when it is exceeded", (length) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(length).toBeGreaterThan(builder.length.max); @@ -414,7 +434,7 @@ describe("Password generator options builder", () => { ])( "should set `options.number === %s` when `options.minNumber` (= %i) is set to a value greater than 0", (expectedNumber, minNumber) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, minNumber }); @@ -425,7 +445,7 @@ describe("Password generator options builder", () => { ); it("should set `options.minNumber` to the minimum value when `options.number` is true", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, number: true }); @@ -435,7 +455,7 @@ describe("Password generator options builder", () => { }); it("should set `options.minNumber` to 0 when `options.number` is false", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, number: false }); @@ -447,7 +467,7 @@ describe("Password generator options builder", () => { it.each([1, 2, 3, 4])( "should set `options.minNumber` (= %i) to the minimum it is less than the minimum number", (minNumber) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.numberCount = 5; // arbitrary value greater than minNumber expect(minNumber).toBeLessThan(policy.numberCount); @@ -463,7 +483,7 @@ describe("Password generator options builder", () => { it.each([1, 3, 5, 7, 9])( "should not change `options.minNumber` (= %i) when it is within the boundaries", (minNumber) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(minNumber).toBeGreaterThanOrEqual(builder.minDigits.min); expect(minNumber).toBeLessThanOrEqual(builder.minDigits.max); @@ -479,7 +499,7 @@ describe("Password generator options builder", () => { it.each([10, 20, 400])( "should set `options.minNumber` (= %i) to the maximum digit boundary when it is exceeded", (minNumber) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(minNumber).toBeGreaterThan(builder.minDigits.max); @@ -501,7 +521,7 @@ describe("Password generator options builder", () => { ])( "should set `options.special === %s` when `options.minSpecial` (= %i) is set to a value greater than 0", (expectedSpecial, minSpecial) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, minSpecial }); @@ -512,7 +532,7 @@ describe("Password generator options builder", () => { ); it("should set `options.minSpecial` to the minimum value when `options.special` is true", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, special: true }); @@ -522,7 +542,7 @@ describe("Password generator options builder", () => { }); it("should set `options.minSpecial` to 0 when `options.special` is false", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, special: false }); @@ -534,7 +554,7 @@ describe("Password generator options builder", () => { it.each([1, 2, 3, 4])( "should set `options.minSpecial` (= %i) to the minimum it is less than the minimum special characters", (minSpecial) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.specialCount = 5; // arbitrary value greater than minSpecial expect(minSpecial).toBeLessThan(policy.specialCount); @@ -550,7 +570,7 @@ describe("Password generator options builder", () => { it.each([1, 3, 5, 7, 9])( "should not change `options.minSpecial` (= %i) when it is within the boundaries", (minSpecial) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(minSpecial).toBeGreaterThanOrEqual(builder.minSpecialCharacters.min); expect(minSpecial).toBeLessThanOrEqual(builder.minSpecialCharacters.max); @@ -566,7 +586,7 @@ describe("Password generator options builder", () => { it.each([10, 20, 400])( "should set `options.minSpecial` (= %i) to the maximum special character boundary when it is exceeded", (minSpecial) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(minSpecial).toBeGreaterThan(builder.minSpecialCharacters.max); @@ -579,7 +599,7 @@ describe("Password generator options builder", () => { ); it("should preserve unknown properties", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ unknown: "property", @@ -602,7 +622,7 @@ describe("Password generator options builder", () => { ])( "should output `options.minLowercase === %i` when `options.lowercase` is %s", (expectedMinLowercase, lowercase) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ lowercase, ...defaultOptions }); @@ -618,7 +638,7 @@ describe("Password generator options builder", () => { ])( "should output `options.minUppercase === %i` when `options.uppercase` is %s", (expectedMinUppercase, uppercase) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ uppercase, ...defaultOptions }); @@ -634,7 +654,7 @@ describe("Password generator options builder", () => { ])( "should output `options.minNumber === %i` when `options.number` is %s and `options.minNumber` is not set", (expectedMinNumber, number) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ number, ...defaultOptions }); @@ -652,7 +672,7 @@ describe("Password generator options builder", () => { ])( "should output `options.number === %s` when `options.minNumber` is %i and `options.number` is not set", (expectedNumber, minNumber) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ minNumber, ...defaultOptions }); @@ -668,7 +688,7 @@ describe("Password generator options builder", () => { ])( "should output `options.minSpecial === %i` when `options.special` is %s and `options.minSpecial` is not set", (special, expectedMinSpecial) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ special, ...defaultOptions }); @@ -686,7 +706,7 @@ describe("Password generator options builder", () => { ])( "should output `options.special === %s` when `options.minSpecial` is %i and `options.special` is not set", (minSpecial, expectedSpecial) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ minSpecial, ...defaultOptions }); @@ -707,7 +727,7 @@ describe("Password generator options builder", () => { const sumOfMinimums = minLowercase + minUppercase + minNumber + minSpecial; expect(sumOfMinimums).toBeLessThan(DefaultPasswordBoundaries.length.min); - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ minLowercase, @@ -732,7 +752,7 @@ describe("Password generator options builder", () => { (expectedMinLength, minLowercase, minUppercase, minNumber, minSpecial) => { expect(expectedMinLength).toBeGreaterThanOrEqual(DefaultPasswordBoundaries.length.min); - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ minLowercase, @@ -749,7 +769,7 @@ describe("Password generator options builder", () => { ); it("should preserve unknown properties", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ unknown: "property", diff --git a/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts b/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts index 5d5430b8cad..7f8dce19b15 100644 --- a/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts +++ b/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts @@ -4,8 +4,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PolicyId } from "@bitwarden/common/types/guid"; -import { Policies } from "../data"; - import { passwordLeastPrivilege } from "./password-least-privilege"; function createPolicy( @@ -22,21 +20,31 @@ function createPolicy( }); } +const disabledValue = Object.freeze({ + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, +}); + describe("passwordLeastPrivilege", () => { it("should return the accumulator when the policy type does not apply", () => { const policy = createPolicy({}, PolicyType.RequireSso); - const result = passwordLeastPrivilege(Policies.Password.disabledValue, policy); + const result = passwordLeastPrivilege(disabledValue, policy); - expect(result).toEqual(Policies.Password.disabledValue); + expect(result).toEqual(disabledValue); }); it("should return the accumulator when the policy is not enabled", () => { const policy = createPolicy({}, PolicyType.PasswordGenerator, false); - const result = passwordLeastPrivilege(Policies.Password.disabledValue, policy); + const result = passwordLeastPrivilege(disabledValue, policy); - expect(result).toEqual(Policies.Password.disabledValue); + expect(result).toEqual(disabledValue); }); it.each([ @@ -50,8 +58,8 @@ describe("passwordLeastPrivilege", () => { ])("should take the %p from the policy", (input, value, expected) => { const policy = createPolicy({ [input]: value }); - const result = passwordLeastPrivilege(Policies.Password.disabledValue, policy); + const result = passwordLeastPrivilege(disabledValue, policy); - expect(result).toEqual({ ...Policies.Password.disabledValue, [expected]: value }); + expect(result).toEqual({ ...disabledValue, [expected]: value }); }); }); diff --git a/libs/tools/generator/core/src/providers/credential-generator-providers.ts b/libs/tools/generator/core/src/providers/credential-generator-providers.ts new file mode 100644 index 00000000000..1e1a8345f34 --- /dev/null +++ b/libs/tools/generator/core/src/providers/credential-generator-providers.ts @@ -0,0 +1,14 @@ +import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider"; + +import { GeneratorDependencyProvider } from "./generator-dependency-provider"; +import { GeneratorMetadataProvider } from "./generator-metadata-provider"; +import { GeneratorProfileProvider } from "./generator-profile-provider"; + +// FIXME: find a better way to manage common dependencies than smashing them all +// together into a mega-type. +export type CredentialGeneratorProviders = { + readonly userState: UserStateSubjectDependencyProvider; + readonly generator: GeneratorDependencyProvider; + readonly profile: GeneratorProfileProvider; + readonly metadata: GeneratorMetadataProvider; +}; diff --git a/libs/tools/generator/core/src/providers/credential-preferences.spec.ts b/libs/tools/generator/core/src/providers/credential-preferences.spec.ts new file mode 100644 index 00000000000..6fd747f3823 --- /dev/null +++ b/libs/tools/generator/core/src/providers/credential-preferences.spec.ts @@ -0,0 +1,105 @@ +import { AlgorithmsByType, Type } from "../metadata"; +import { CredentialPreference } from "../types"; + +import { PREFERENCES } from "./credential-preferences"; + +const SomeCredentialPreferences: CredentialPreference = Object.freeze({ + email: Object.freeze({ + algorithm: AlgorithmsByType[Type.email][0], + updated: new Date(0), + }), + password: Object.freeze({ + algorithm: AlgorithmsByType[Type.password][0], + updated: new Date(0), + }), + username: Object.freeze({ + algorithm: AlgorithmsByType[Type.username][0], + updated: new Date(0), + }), +}); + +describe("PREFERENCES", () => { + describe("deserializer", () => { + it.each([[null], [undefined]])("creates new preferences (= %p)", (value) => { + // this case tests what happens when the type system is bypassed + const result = PREFERENCES.deserializer(value!); + + expect(result).toMatchObject({ + email: { + algorithm: AlgorithmsByType[Type.email][0], + }, + password: { + algorithm: AlgorithmsByType[Type.password][0], + }, + username: { + algorithm: AlgorithmsByType[Type.username][0], + }, + }); + }); + + it("fills missing password preferences", () => { + const input: any = structuredClone(SomeCredentialPreferences); + delete input.password; + + const result = PREFERENCES.deserializer(input); + + expect(result).toMatchObject({ + password: { + algorithm: AlgorithmsByType[Type.password][0], + }, + }); + }); + + it("fills missing email preferences", () => { + const input: any = structuredClone(SomeCredentialPreferences); + delete input.email; + + const result = PREFERENCES.deserializer(input); + + expect(result).toMatchObject({ + email: { + algorithm: AlgorithmsByType[Type.email][0], + }, + }); + }); + + it("fills missing username preferences", () => { + const input: any = structuredClone(SomeCredentialPreferences); + delete input.username; + + const result = PREFERENCES.deserializer(input); + + expect(result).toMatchObject({ + username: { + algorithm: AlgorithmsByType[Type.username][0], + }, + }); + }); + + it("converts string fields to Dates", () => { + const input: any = structuredClone(SomeCredentialPreferences); + input.email.updated = "1970-01-01T00:00:00.100Z"; + input.password.updated = "1970-01-01T00:00:00.200Z"; + input.username.updated = "1970-01-01T00:00:00.300Z"; + + const result = PREFERENCES.deserializer(input); + + expect(result?.email.updated).toEqual(new Date(100)); + expect(result?.password.updated).toEqual(new Date(200)); + expect(result?.username.updated).toEqual(new Date(300)); + }); + + it("converts number fields to Dates", () => { + const input: any = structuredClone(SomeCredentialPreferences); + input.email.updated = 100; + input.password.updated = 200; + input.username.updated = 300; + + const result = PREFERENCES.deserializer(input); + + expect(result?.email.updated).toEqual(new Date(100)); + expect(result?.password.updated).toEqual(new Date(200)); + expect(result?.username.updated).toEqual(new Date(300)); + }); + }); +}); diff --git a/libs/tools/generator/core/src/providers/credential-preferences.ts b/libs/tools/generator/core/src/providers/credential-preferences.ts new file mode 100644 index 00000000000..5c6efd6008b --- /dev/null +++ b/libs/tools/generator/core/src/providers/credential-preferences.ts @@ -0,0 +1,28 @@ +import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; + +import { AlgorithmsByType, CredentialType } from "../metadata"; +import { CredentialPreference } from "../types"; + +/** plaintext password generation options */ +export const PREFERENCES = new UserKeyDefinition( + GENERATOR_DISK, + "credentialPreferences", + { + deserializer: (value) => { + const result = (value as any) ?? {}; + + for (const key in AlgorithmsByType) { + const type = key as CredentialType; + if (result[type]) { + result[type].updated = new Date(result[type].updated); + } else { + const [algorithm] = AlgorithmsByType[type]; + result[type] = { algorithm, updated: new Date() }; + } + } + + return result; + }, + clearOn: ["logout"], + }, +); diff --git a/libs/tools/generator/core/src/providers/generator-dependency-provider.ts b/libs/tools/generator/core/src/providers/generator-dependency-provider.ts new file mode 100644 index 00000000000..14942698cdb --- /dev/null +++ b/libs/tools/generator/core/src/providers/generator-dependency-provider.ts @@ -0,0 +1,12 @@ +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { RestClient } from "@bitwarden/common/tools/integration/rpc"; + +import { Randomizer } from "../abstractions"; + +export type GeneratorDependencyProvider = { + randomizer: Randomizer; + client: RestClient; + // FIXME: introduce `I18nKeyOrLiteral` into forwarder + // structures and remove this dependency + i18nService: I18nService; +}; diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts b/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts similarity index 98% rename from libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts rename to libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts index 37a987f88bc..71fced46fa6 100644 --- a/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts +++ b/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts @@ -75,6 +75,7 @@ const SystemProvider = { } as LegacyEncryptorProvider, state: SomeStateProvider, log: disabledSemanticLoggerProvider, + now: Date.now, } as UserStateSubjectDependencyProvider; const SomeSiteId: SiteId = Site.forwarder; @@ -415,14 +416,14 @@ describe("GeneratorMetadataProvider", () => { await expect(firstValueFrom(result)).resolves.toEqual(plusAddress.id); }); - it("emits undefined when the user's preference is unavailable and there is no metadata", async () => { + it("emits the original preference when the user's preference is unavailable and there is no metadata", async () => { SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([])); const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []); const result = new ReplaySubject(1); provider.preference$(Type.email, { account$: SomeAccount$ }).subscribe(result); - await expect(firstValueFrom(result)).resolves.toBeUndefined(); + await expect(firstValueFrom(result)).resolves.toEqual(preferences[Type.email].algorithm); }); }); diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.ts b/libs/tools/generator/core/src/providers/generator-metadata-provider.ts similarity index 87% rename from libs/tools/generator/core/src/services/generator-metadata-provider.ts rename to libs/tools/generator/core/src/providers/generator-metadata-provider.ts index 161f7192c39..52901545023 100644 --- a/libs/tools/generator/core/src/services/generator-metadata-provider.ts +++ b/libs/tools/generator/core/src/providers/generator-metadata-provider.ts @@ -1,12 +1,4 @@ -import { - Observable, - combineLatestWith, - distinctUntilChanged, - map, - shareReplay, - switchMap, - takeUntil, -} from "rxjs"; +import { Observable, distinctUntilChanged, map, shareReplay, switchMap, takeUntil } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; @@ -14,7 +6,7 @@ import { BoundDependency } from "@bitwarden/common/tools/dependencies"; import { ExtensionSite } from "@bitwarden/common/tools/extension"; import { SemanticLogger } from "@bitwarden/common/tools/log"; import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; -import { anyComplete, pin } from "@bitwarden/common/tools/rx"; +import { anyComplete, memoizedMap, pin } from "@bitwarden/common/tools/rx"; import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject"; import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider"; @@ -29,7 +21,7 @@ import { Algorithms, Types, } from "../metadata"; -import { availableAlgorithms_vNext } from "../policies/available-algorithms-policy"; +import { AvailableAlgorithmsConstraint, availableAlgorithms } from "../policies"; import { CredentialPreference } from "../types"; import { AlgorithmRequest, @@ -148,8 +140,15 @@ export class GeneratorMetadataProvider { const policies$ = this.application.policy .policiesByType$(PolicyType.PasswordGenerator, id) .pipe( - map((p) => availableAlgorithms_vNext(p).filter((a) => this._metadata.has(a))), - map((p) => new Set(p)), + map((p) => + availableAlgorithms(p) + .filter((a) => this._metadata.has(a)) + .sort(), + ), + // interning the set transformation lets `distinctUntilChanged()` eliminate + // repeating policy emissions using reference equality + memoizedMap((a) => new Set(a), { key: (a) => a.join(":") }), + distinctUntilChanged(), // complete policy emissions otherwise `switchMap` holds `available$` open indefinitely takeUntil(anyComplete(id$)), ); @@ -211,24 +210,7 @@ export class GeneratorMetadataProvider { const account$ = dependencies.account$.pipe(shareReplay({ bufferSize: 1, refCount: true })); const algorithm$ = this.preferences({ account$ }).pipe( - combineLatestWith(this.isAvailable$({ account$ })), - map(([preferences, isAvailable]) => { - const algorithm: CredentialAlgorithm = preferences[type].algorithm; - if (isAvailable(algorithm)) { - return algorithm; - } - - const algorithms = type ? this.algorithms({ type: type }) : []; - // `?? null` because logging types must be `Jsonify` - const defaultAlgorithm = algorithms.find(isAvailable) ?? null; - this.log.debug( - { algorithm, defaultAlgorithm, credentialType: type }, - "preference not available; defaulting the generator algorithm", - ); - - // `?? undefined` so that interface is ADR-14 compliant - return defaultAlgorithm ?? undefined; - }), + map((preferences) => preferences[type].algorithm), distinctUntilChanged(), ); @@ -246,8 +228,16 @@ export class GeneratorMetadataProvider { preferences( dependencies: BoundDependency<"account", Account>, ): UserStateSubject { - // FIXME: enforce policy - const subject = new UserStateSubject(PREFERENCES, this.system, dependencies); + const account$ = dependencies.account$.pipe(shareReplay({ bufferSize: 1, refCount: true })); + + const constraints$ = this.isAvailable$({ account$ }).pipe( + map( + (isAvailable) => + new AvailableAlgorithmsConstraint(this.algorithms.bind(this), isAvailable, this.system), + ), + ); + + const subject = new UserStateSubject(PREFERENCES, this.system, { account$, constraints$ }); return subject; } diff --git a/libs/tools/generator/core/src/services/generator-profile-provider.spec.ts b/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts similarity index 99% rename from libs/tools/generator/core/src/services/generator-profile-provider.spec.ts rename to libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts index aeb1a648a14..1053834eca7 100644 --- a/libs/tools/generator/core/src/services/generator-profile-provider.spec.ts +++ b/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts @@ -67,6 +67,7 @@ const dependencyProvider: UserStateSubjectDependencyProvider = { encryptor: encryptorProvider, state: stateProvider, log: disabledSemanticLoggerProvider, + now: Date.now, }; // settings storage location diff --git a/libs/tools/generator/core/src/services/generator-profile-provider.ts b/libs/tools/generator/core/src/providers/generator-profile-provider.ts similarity index 94% rename from libs/tools/generator/core/src/services/generator-profile-provider.ts rename to libs/tools/generator/core/src/providers/generator-profile-provider.ts index 7088e23d3fe..4117d1f2a78 100644 --- a/libs/tools/generator/core/src/services/generator-profile-provider.ts +++ b/libs/tools/generator/core/src/providers/generator-profile-provider.ts @@ -19,6 +19,7 @@ import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/stat import { ProfileContext, CoreProfileMetadata, ProfileMetadata } from "../metadata"; import { GeneratorConstraints } from "../types/generator-constraints"; +import { equivalent } from "../util"; /** Surfaces contextual information to credential generators */ export class GeneratorProfileProvider { @@ -99,7 +100,10 @@ export class GeneratorProfileProvider { const constraints$ = policies$.pipe( map((policies) => profile.constraints.create(policies, context)), - tap(() => this.log.debug("constraints created")), + distinctUntilChanged((previous, next) => { + return equivalent(previous, next); + }), + tap((constraints) => this.log.debug(constraints as object, "constraints updated")), ); return constraints$; diff --git a/libs/tools/generator/core/src/providers/index.ts b/libs/tools/generator/core/src/providers/index.ts new file mode 100644 index 00000000000..bad56b746b6 --- /dev/null +++ b/libs/tools/generator/core/src/providers/index.ts @@ -0,0 +1,4 @@ +export { CredentialGeneratorProviders } from "./credential-generator-providers"; +export { GeneratorMetadataProvider } from "./generator-metadata-provider"; +export { GeneratorProfileProvider } from "./generator-profile-provider"; +export { GeneratorDependencyProvider } from "./generator-dependency-provider"; diff --git a/libs/tools/generator/core/src/rx.ts b/libs/tools/generator/core/src/rx.ts index 44d23ef1c5c..ab907b6455f 100644 --- a/libs/tools/generator/core/src/rx.ts +++ b/libs/tools/generator/core/src/rx.ts @@ -18,20 +18,6 @@ export function mapPolicyToEvaluator( ); } -/** Maps an administrative console policy to constraints using the provided configuration. - * @param configuration the configuration that constructs the constraints. - */ -export function mapPolicyToConstraints( - configuration: PolicyConfiguration, - email: string, -) { - return pipe( - reduceCollection(configuration.combine, configuration.disabledValue), - distinctIfShallowMatch(), - map((policy) => configuration.toConstraints(policy, email)), - ); -} - /** Constructs a method that maps a policy to the default (no-op) policy. */ export function newDefaultEvaluator() { return () => { diff --git a/libs/tools/generator/core/src/services/credential-generator.service.spec.ts b/libs/tools/generator/core/src/services/credential-generator.service.spec.ts deleted file mode 100644 index 2bc8d514873..00000000000 --- a/libs/tools/generator/core/src/services/credential-generator.service.spec.ts +++ /dev/null @@ -1,1050 +0,0 @@ -// FIXME: remove ts-strict-ignore once `FakeAccountService` implements ts strict support -// @ts-strict-ignore -import { mock } from "jest-mock-extended"; -import { BehaviorSubject, firstValueFrom, map, Subject } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { LegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/legacy-encryptor-provider"; -import { UserEncryptor } from "@bitwarden/common/tools/cryptography/user-encryptor.abstraction"; -import { disabledSemanticLoggerProvider } from "@bitwarden/common/tools/log"; -import { StateConstraints } from "@bitwarden/common/tools/types"; -import { OrganizationId, PolicyId, UserId } from "@bitwarden/common/types/guid"; - -import { - FakeStateProvider, - FakeAccountService, - awaitAsync, - ObservableTracker, -} from "../../../../../common/spec"; -import { Randomizer } from "../abstractions"; -import { Generators } from "../data"; -import { - CredentialGeneratorConfiguration, - GeneratedCredential, - GenerateRequest, - GeneratorConstraints, -} from "../types"; - -import { CredentialGeneratorService } from "./credential-generator.service"; - -// arbitrary settings types -type SomeSettings = { foo: string }; -type SomePolicy = { fooPolicy: boolean }; - -// settings storage location -const SettingsKey = new UserKeyDefinition(GENERATOR_DISK, "SomeSettings", { - deserializer: (value) => value, - clearOn: [], -}); - -// fake policies -const policyService = mock(); -const somePolicy = new Policy({ - data: { fooPolicy: true }, - type: PolicyType.PasswordGenerator, - id: "" as PolicyId, - organizationId: "" as OrganizationId, - enabled: true, -}); -const passwordOverridePolicy = new Policy({ - id: "" as PolicyId, - organizationId: "", - type: PolicyType.PasswordGenerator, - data: { - overridePasswordType: "password", - }, - enabled: true, -}); - -const passphraseOverridePolicy = new Policy({ - id: "" as PolicyId, - organizationId: "", - type: PolicyType.PasswordGenerator, - data: { - overridePasswordType: "passphrase", - }, - enabled: true, -}); - -const SomeTime = new Date(1); -const SomeAlgorithm = "passphrase"; -const SomeCategory = "password"; -const SomeNameKey = "passphraseKey"; -const SomeGenerateKey = "generateKey"; -const SomeCredentialTypeKey = "credentialTypeKey"; -const SomeOnGeneratedMessageKey = "onGeneratedMessageKey"; -const SomeCopyKey = "copyKey"; -const SomeUseGeneratedValueKey = "useGeneratedValueKey"; - -// fake the configuration -const SomeConfiguration: CredentialGeneratorConfiguration = { - id: SomeAlgorithm, - category: SomeCategory, - nameKey: SomeNameKey, - generateKey: SomeGenerateKey, - onGeneratedMessageKey: SomeOnGeneratedMessageKey, - credentialTypeKey: SomeCredentialTypeKey, - copyKey: SomeCopyKey, - useGeneratedValueKey: SomeUseGeneratedValueKey, - onlyOnRequest: false, - request: [], - engine: { - create: (_randomizer) => { - return { - generate: (request, settings) => { - const result = new GeneratedCredential( - settings.foo, - SomeAlgorithm, - SomeTime, - request.source, - request.website, - ); - return Promise.resolve(result); - }, - }; - }, - }, - settings: { - initial: { foo: "initial" }, - constraints: { foo: {} }, - account: SettingsKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: { - fooPolicy: false, - }, - combine: (acc, policy) => { - return { fooPolicy: acc.fooPolicy || policy.data.fooPolicy }; - }, - createEvaluator: () => { - throw new Error("this should never be called"); - }, - toConstraints: (policy) => { - if (policy.fooPolicy) { - return { - constraints: { - policyInEffect: true, - }, - calibrate(state: SomeSettings) { - return { - constraints: {}, - adjust(state: SomeSettings) { - return { foo: `adjusted(${state.foo})` }; - }, - fix(state: SomeSettings) { - return { foo: `fixed(${state.foo})` }; - }, - } satisfies StateConstraints; - }, - } satisfies GeneratorConstraints; - } else { - return { - constraints: { - policyInEffect: false, - }, - adjust(state: SomeSettings) { - return state; - }, - fix(state: SomeSettings) { - return state; - }, - } satisfies GeneratorConstraints; - } - }, - }, -}; - -// fake user information -const SomeUser = "SomeUser" as UserId; -const AnotherUser = "SomeOtherUser" as UserId; -const accounts = { - [SomeUser]: { - id: SomeUser, - name: "some user", - email: "some.user@example.com", - emailVerified: true, - }, - [AnotherUser]: { - id: AnotherUser, - name: "some other user", - email: "some.other.user@example.com", - emailVerified: true, - }, -}; -const accountService = new FakeAccountService(accounts); - -// fake state -const stateProvider = new FakeStateProvider(accountService); - -// fake randomizer -const randomizer = mock(); - -const i18nService = mock(); - -const apiService = mock(); - -const encryptor = mock(); -const encryptorProvider = mock({ - userEncryptor$(_, dependencies) { - return dependencies.singleUserId$.pipe(map((userId) => ({ userId, encryptor }))); - }, -}); - -const account$ = new BehaviorSubject(accounts[SomeUser]); - -const providers = { - encryptor: encryptorProvider, - state: stateProvider, - log: disabledSemanticLoggerProvider, -}; - -describe("CredentialGeneratorService", () => { - beforeEach(async () => { - await accountService.switchAccount(SomeUser); - policyService.policiesByType$.mockImplementation(() => new BehaviorSubject([]).asObservable()); - i18nService.t.mockImplementation((key: string) => key); - apiService.fetch.mockImplementation(() => Promise.resolve(mock())); - jest.clearAllMocks(); - }); - - describe("generate$", () => { - it("completes when `on$` completes", async () => { - await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new Subject(); - let complete = false; - - // confirm no emission during subscription - generator.generate$(SomeConfiguration, { on$, account$ }).subscribe({ - complete: () => { - complete = true; - }, - }); - on$.complete(); - await awaitAsync(); - - expect(complete).toBeTruthy(); - }); - - it("includes request.source in the generated credential", async () => { - const settings = { foo: "value" }; - await stateProvider.setUserState(SettingsKey, settings, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new BehaviorSubject({ source: "some source" }); - const generated = new ObservableTracker( - generator.generate$(SomeConfiguration, { on$, account$ }), - ); - - const result = await generated.expectEmission(); - - expect(result.source).toEqual("some source"); - }); - - it("includes request.website in the generated credential", async () => { - const settings = { foo: "value" }; - await stateProvider.setUserState(SettingsKey, settings, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new BehaviorSubject({ website: "some website" }); - const generated = new ObservableTracker( - generator.generate$(SomeConfiguration, { on$, account$ }), - ); - - const result = await generated.expectEmission(); - - expect(result.website).toEqual("some website"); - }); - - // FIXME: test these when the fake state provider can create the required emissions - it.todo("errors when the settings error"); - it.todo("completes when the settings complete"); - - it("emits a generation for a specific user when `user$` supplied", async () => { - await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser); - await stateProvider.setUserState(SettingsKey, { foo: "another" }, AnotherUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account$ = new BehaviorSubject(accounts[AnotherUser]).asObservable(); - const on$ = new Subject(); - const generated = new ObservableTracker( - generator.generate$(SomeConfiguration, { on$, account$ }), - ); - on$.next({}); - - const result = await generated.expectEmission(); - - expect(result).toEqual(new GeneratedCredential("another", SomeAlgorithm, SomeTime)); - }); - - it("errors when `user$` errors", async () => { - await stateProvider.setUserState(SettingsKey, null, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new Subject(); - const account$ = new BehaviorSubject(accounts[SomeUser]); - let error = null; - - generator.generate$(SomeConfiguration, { on$, account$ }).subscribe({ - error: (e: unknown) => { - error = e; - }, - }); - account$.error({ some: "error" }); - await awaitAsync(); - - expect(error).toEqual({ some: "error" }); - }); - - it("completes when `user$` completes", async () => { - await stateProvider.setUserState(SettingsKey, null, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new Subject(); - const account$ = new BehaviorSubject(accounts[SomeUser]); - let completed = false; - - generator.generate$(SomeConfiguration, { on$, account$ }).subscribe({ - complete: () => { - completed = true; - }, - }); - account$.complete(); - await awaitAsync(); - - expect(completed).toBeTruthy(); - }); - - it("emits a generation only when `on$` emits", async () => { - // This test breaks from arrange/act/assert because it is testing causality - await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new Subject(); - const results: any[] = []; - - // confirm no emission during subscription - const sub = generator - .generate$(SomeConfiguration, { on$, account$ }) - .subscribe((result) => results.push(result)); - await awaitAsync(); - expect(results.length).toEqual(0); - - // confirm forwarded emission - on$.next({}); - await awaitAsync(); - expect(results).toEqual([new GeneratedCredential("value", SomeAlgorithm, SomeTime)]); - - // confirm updating settings does not cause an emission - await stateProvider.setUserState(SettingsKey, { foo: "next" }, SomeUser); - await awaitAsync(); - expect(results.length).toBe(1); - - // confirm forwarded emission takes latest value - on$.next({}); - await awaitAsync(); - sub.unsubscribe(); - - expect(results).toEqual([ - new GeneratedCredential("value", SomeAlgorithm, SomeTime), - new GeneratedCredential("next", SomeAlgorithm, SomeTime), - ]); - }); - - it("errors when `on$` errors", async () => { - await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new Subject(); - let error: any = null; - - // confirm no emission during subscription - generator.generate$(SomeConfiguration, { on$, account$ }).subscribe({ - error: (e: unknown) => { - error = e; - }, - }); - on$.error({ some: "error" }); - await awaitAsync(); - - expect(error).toEqual({ some: "error" }); - }); - - // FIXME: test these when the fake state provider can delay its first emission - it.todo("emits when settings$ become available if on$ is called before they're ready."); - }); - - describe("algorithms", () => { - it("outputs password generation metadata", () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = generator.algorithms("password"); - - expect(result.some((a) => a.id === Generators.password.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.passphrase.id)).toBeTruthy(); - - // this test shouldn't contain entries outside of the current category - expect(result.some((a) => a.id === Generators.username.id)).toBeFalsy(); - expect(result.some((a) => a.id === Generators.catchall.id)).toBeFalsy(); - }); - - it("outputs username generation metadata", () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = generator.algorithms("username"); - - expect(result.some((a) => a.id === Generators.username.id)).toBeTruthy(); - - // this test shouldn't contain entries outside of the current category - expect(result.some((a) => a.id === Generators.catchall.id)).toBeFalsy(); - expect(result.some((a) => a.id === Generators.password.id)).toBeFalsy(); - }); - - it("outputs email generation metadata", () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = generator.algorithms("email"); - - expect(result.some((a) => a.id === Generators.catchall.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.subaddress.id)).toBeTruthy(); - - // this test shouldn't contain entries outside of the current category - expect(result.some((a) => a.id === Generators.username.id)).toBeFalsy(); - expect(result.some((a) => a.id === Generators.password.id)).toBeFalsy(); - }); - - it("combines metadata across categories", () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = generator.algorithms(["username", "email"]); - - expect(result.some((a) => a.id === Generators.username.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.catchall.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.subaddress.id)).toBeTruthy(); - - // this test shouldn't contain entries outside of the current categories - expect(result.some((a) => a.id === Generators.password.id)).toBeFalsy(); - }); - }); - - describe("algorithms$", () => { - // these tests cannot use the observable tracker because they return - // data that cannot be cloned - it("returns password metadata", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.algorithms$("password", { account$ })); - - expect(result.some((a) => a.id === Generators.password.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.passphrase.id)).toBeTruthy(); - }); - - it("returns username metadata", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.algorithms$("username", { account$ })); - - expect(result.some((a) => a.id === Generators.username.id)).toBeTruthy(); - }); - - it("returns email metadata", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.algorithms$("email", { account$ })); - - expect(result.some((a) => a.id === Generators.catchall.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.subaddress.id)).toBeTruthy(); - }); - - it("returns username and email metadata", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom( - generator.algorithms$(["username", "email"], { account$ }), - ); - - expect(result.some((a) => a.id === Generators.username.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.catchall.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.subaddress.id)).toBeTruthy(); - }); - - // Subsequent tests focus on passwords and passphrases as an example of policy - // awareness; they exercise the logic without being comprehensive - it("enforces the active user's policy", async () => { - const policy$ = new BehaviorSubject([passwordOverridePolicy]); - policyService.policiesByType$.mockReturnValue(policy$); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.algorithms$(["password"], { account$ })); - - expect(policyService.policiesByType$).toHaveBeenCalledWith( - PolicyType.PasswordGenerator, - SomeUser, - ); - expect(result.some((a) => a.id === Generators.password.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.passphrase.id)).toBeFalsy(); - }); - - it("follows changes to the active user", async () => { - const account$ = new BehaviorSubject(accounts[SomeUser]); - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passphraseOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const results: any = []; - const sub = generator.algorithms$("password", { account$ }).subscribe((r) => results.push(r)); - - account$.next(accounts[AnotherUser]); - await awaitAsync(); - sub.unsubscribe(); - - const [someResult, anotherResult] = results; - - expect(policyService.policiesByType$).toHaveBeenNthCalledWith( - 1, - PolicyType.PasswordGenerator, - SomeUser, - ); - expect(someResult.some((a: any) => a.id === Generators.password.id)).toBeTruthy(); - expect(someResult.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy(); - - expect(policyService.policiesByType$).toHaveBeenNthCalledWith( - 2, - PolicyType.PasswordGenerator, - AnotherUser, - ); - expect(anotherResult.some((a: any) => a.id === Generators.passphrase.id)).toBeTruthy(); - expect(anotherResult.some((a: any) => a.id === Generators.password.id)).toBeFalsy(); - }); - - it("reads an arbitrary user's settings", async () => { - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account$ = new BehaviorSubject(accounts[AnotherUser]).asObservable(); - - const result = await firstValueFrom(generator.algorithms$("password", { account$ })); - - expect(policyService.policiesByType$).toHaveBeenCalledWith( - PolicyType.PasswordGenerator, - AnotherUser, - ); - expect(result.some((a: any) => a.id === Generators.password.id)).toBeTruthy(); - expect(result.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy(); - }); - - it("follows changes to the arbitrary user", async () => { - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passphraseOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - const results: any = []; - const sub = generator.algorithms$("password", { account$ }).subscribe((r) => results.push(r)); - - account.next(accounts[AnotherUser]); - await awaitAsync(); - sub.unsubscribe(); - - const [someResult, anotherResult] = results; - expect(policyService.policiesByType$).toHaveBeenCalledWith( - PolicyType.PasswordGenerator, - SomeUser, - ); - expect(someResult.some((a: any) => a.id === Generators.password.id)).toBeTruthy(); - expect(someResult.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy(); - - expect(policyService.policiesByType$).toHaveBeenCalledWith( - PolicyType.PasswordGenerator, - AnotherUser, - ); - expect(anotherResult.some((a: any) => a.id === Generators.passphrase.id)).toBeTruthy(); - expect(anotherResult.some((a: any) => a.id === Generators.password.id)).toBeFalsy(); - }); - - it("errors when the arbitrary user's stream errors", async () => { - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - let error = null; - - generator.algorithms$("password", { account$ }).subscribe({ - error: (e: unknown) => { - error = e; - }, - }); - account.error({ some: "error" }); - await awaitAsync(); - - expect(error).toEqual({ some: "error" }); - }); - - it("completes when the arbitrary user's stream completes", async () => { - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - let completed = false; - - generator.algorithms$("password", { account$ }).subscribe({ - complete: () => { - completed = true; - }, - }); - account.complete(); - await awaitAsync(); - - expect(completed).toBeTruthy(); - }); - - it("ignores repeated arbitrary user emissions", async () => { - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - let count = 0; - - const sub = generator.algorithms$("password", { account$ }).subscribe({ - next: () => { - count++; - }, - }); - await awaitAsync(); - account.next(accounts[SomeUser]); - await awaitAsync(); - account.next(accounts[SomeUser]); - await awaitAsync(); - sub.unsubscribe(); - - expect(count).toEqual(1); - }); - }); - - describe("settings$", () => { - it("defaults to the configuration's initial settings if settings aren't found", async () => { - await stateProvider.setUserState(SettingsKey, null, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.settings$(SomeConfiguration, { account$ })); - - expect(result).toEqual(SomeConfiguration.settings.initial); - }); - - it("reads from the active user's configuration-defined storage", async () => { - const settings = { foo: "value" }; - await stateProvider.setUserState(SettingsKey, settings, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.settings$(SomeConfiguration, { account$ })); - - expect(result).toEqual(settings); - }); - - it("applies policy to the loaded settings", async () => { - const settings = { foo: "value" }; - await stateProvider.setUserState(SettingsKey, settings, SomeUser); - const policy$ = new BehaviorSubject([somePolicy]); - policyService.policiesByType$.mockReturnValue(policy$); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.settings$(SomeConfiguration, { account$ })); - - expect(result).toEqual({ foo: "adjusted(value)" }); - }); - - it("reads an arbitrary user's settings", async () => { - await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser); - const anotherSettings = { foo: "another" }; - await stateProvider.setUserState(SettingsKey, anotherSettings, AnotherUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account$ = new BehaviorSubject(accounts[AnotherUser]).asObservable(); - - const result = await firstValueFrom(generator.settings$(SomeConfiguration, { account$ })); - - expect(result).toEqual(anotherSettings); - }); - - it("errors when the arbitrary user's stream errors", async () => { - await stateProvider.setUserState(SettingsKey, null, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - let error = null; - - generator.settings$(SomeConfiguration, { account$ }).subscribe({ - error: (e: unknown) => { - error = e; - }, - }); - account.error({ some: "error" }); - await awaitAsync(); - - expect(error).toEqual({ some: "error" }); - }); - - it("completes when the arbitrary user's stream completes", async () => { - await stateProvider.setUserState(SettingsKey, null, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - let completed = false; - - generator.settings$(SomeConfiguration, { account$ }).subscribe({ - complete: () => { - completed = true; - }, - }); - account.complete(); - await awaitAsync(); - - expect(completed).toBeTruthy(); - }); - }); - - describe("settings", () => { - it("writes to the user's state", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const subject = generator.settings(SomeConfiguration, { account$ }); - - subject.next({ foo: "next value" }); - await awaitAsync(); - const result = await firstValueFrom(stateProvider.getUserState$(SettingsKey, SomeUser)); - - expect(result).toEqual({ - foo: "next value", - }); - }); - }); - - describe("policy$", () => { - it("creates constraints without policy in effect when there is no policy", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable(); - - const result = await firstValueFrom(generator.policy$(SomeConfiguration, { account$ })); - - expect(result.constraints.policyInEffect).toBeFalsy(); - }); - - it("creates constraints with policy in effect when there is a policy", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable(); - const policy$ = new BehaviorSubject([somePolicy]); - policyService.policiesByType$.mockReturnValue(policy$); - - const result = await firstValueFrom(generator.policy$(SomeConfiguration, { account$ })); - - expect(result.constraints.policyInEffect).toBeTruthy(); - }); - - it("follows policy emissions", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - const somePolicySubject = new BehaviorSubject([somePolicy]); - policyService.policiesByType$.mockReturnValueOnce(somePolicySubject.asObservable()); - const emissions: GeneratorConstraints[] = []; - const sub = generator - .policy$(SomeConfiguration, { account$ }) - .subscribe((policy) => emissions.push(policy)); - - // swap the active policy for an inactive policy - somePolicySubject.next([]); - await awaitAsync(); - sub.unsubscribe(); - const [someResult, anotherResult] = emissions; - - expect(someResult.constraints.policyInEffect).toBeTruthy(); - expect(anotherResult.constraints.policyInEffect).toBeFalsy(); - }); - - it("follows user emissions", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - const somePolicy$ = new BehaviorSubject([somePolicy]).asObservable(); - const anotherPolicy$ = new BehaviorSubject([]).asObservable(); - policyService.policiesByType$ - .mockReturnValueOnce(somePolicy$) - .mockReturnValueOnce(anotherPolicy$); - const emissions: GeneratorConstraints[] = []; - const sub = generator - .policy$(SomeConfiguration, { account$ }) - .subscribe((policy) => emissions.push(policy)); - - // swapping the user invokes the return for `anotherPolicy$` - account.next(accounts[AnotherUser]); - await awaitAsync(); - sub.unsubscribe(); - const [someResult, anotherResult] = emissions; - - expect(someResult.constraints.policyInEffect).toBeTruthy(); - expect(anotherResult.constraints.policyInEffect).toBeFalsy(); - }); - - it("errors when the user errors", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - const expectedError = { some: "error" }; - - let actualError: any = null; - generator.policy$(SomeConfiguration, { account$ }).subscribe({ - error: (e: unknown) => { - actualError = e; - }, - }); - account.error(expectedError); - await awaitAsync(); - - expect(actualError).toEqual(expectedError); - }); - - it("completes when the user completes", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - - let completed = false; - generator.policy$(SomeConfiguration, { account$ }).subscribe({ - complete: () => { - completed = true; - }, - }); - account.complete(); - await awaitAsync(); - - expect(completed).toBeTruthy(); - }); - }); -}); diff --git a/libs/tools/generator/core/src/services/credential-generator.service.ts b/libs/tools/generator/core/src/services/credential-generator.service.ts deleted file mode 100644 index eacc2ca6fc5..00000000000 --- a/libs/tools/generator/core/src/services/credential-generator.service.ts +++ /dev/null @@ -1,296 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { concatMap, distinctUntilChanged, map, Observable, switchMap, takeUntil } from "rxjs"; -import { Simplify } from "type-fest"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Account } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { BoundDependency, OnDependency } from "@bitwarden/common/tools/dependencies"; -import { IntegrationMetadata } from "@bitwarden/common/tools/integration"; -import { RestClient } from "@bitwarden/common/tools/integration/rpc"; -import { anyComplete, withLatestReady } from "@bitwarden/common/tools/rx"; -import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject"; -import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider"; - -import { Randomizer } from "../abstractions"; -import { - Generators, - getForwarderConfiguration, - Integrations, - toCredentialGeneratorConfiguration, -} from "../data"; -import { availableAlgorithms } from "../policies/available-algorithms-policy"; -import { mapPolicyToConstraints } from "../rx"; -import { - CredentialAlgorithm, - CredentialCategories, - CredentialCategory, - AlgorithmInfo, - CredentialPreference, - isForwarderIntegration, - ForwarderIntegration, - GenerateRequest, -} from "../types"; -import { - CredentialGeneratorConfiguration as Configuration, - CredentialGeneratorInfo, - GeneratorDependencyProvider, -} from "../types/credential-generator-configuration"; -import { GeneratorConstraints } from "../types/generator-constraints"; - -import { PREFERENCES } from "./credential-preferences"; - -type Generate$Dependencies = Simplify< - OnDependency & BoundDependency<"account", Account> ->; - -export class CredentialGeneratorService { - constructor( - private readonly randomizer: Randomizer, - private readonly policyService: PolicyService, - private readonly apiService: ApiService, - private readonly i18nService: I18nService, - private readonly providers: UserStateSubjectDependencyProvider, - ) {} - - private getDependencyProvider(): GeneratorDependencyProvider { - return { - client: new RestClient(this.apiService, this.i18nService), - i18nService: this.i18nService, - randomizer: this.randomizer, - }; - } - - // FIXME: the rxjs methods of this service can be a lot more resilient if - // `Subjects` are introduced where sharing occurs - - /** Generates a stream of credentials - * @param configuration determines which generator's settings are loaded - * @param dependencies.on$ Required. A new credential is emitted when this emits. - */ - generate$( - configuration: Readonly>, - dependencies: Generate$Dependencies, - ) { - const engine = configuration.engine.create(this.getDependencyProvider()); - const settings$ = this.settings$(configuration, dependencies); - - // generation proper - const generate$ = dependencies.on$.pipe( - withLatestReady(settings$), - concatMap(([request, settings]) => engine.generate(request, settings)), - takeUntil(anyComplete([settings$])), - ); - - return generate$; - } - - /** Emits metadata concerning the provided generation algorithms - * @param category the category or categories of interest - * @param dependences.account$ algorithms are filtered to only - * those matching the provided account's policy. - * @returns An observable that emits algorithm metadata. - */ - algorithms$( - category: CredentialCategory, - dependencies: BoundDependency<"account", Account>, - ): Observable; - algorithms$( - category: CredentialCategory[], - dependencies: BoundDependency<"account", Account>, - ): Observable; - algorithms$( - category: CredentialCategory | CredentialCategory[], - dependencies: BoundDependency<"account", Account>, - ) { - // any cast required here because TypeScript fails to bind `category` - // to the union-typed overload of `algorithms`. - const algorithms = this.algorithms(category as any); - - // apply policy - const algorithms$ = dependencies.account$.pipe( - distinctUntilChanged(), - switchMap((account) => { - const policies$ = this.policyService - .policiesByType$(PolicyType.PasswordGenerator, account.id) - .pipe( - map((p) => new Set(availableAlgorithms(p))), - // complete policy emissions otherwise `switchMap` holds `algorithms$` open indefinitely - takeUntil(anyComplete(dependencies.account$)), - ); - return policies$; - }), - map((available) => { - const filtered = algorithms.filter( - (c) => isForwarderIntegration(c.id) || available.has(c.id), - ); - return filtered; - }), - ); - - return algorithms$; - } - - /** Lists metadata for the algorithms in a credential category - * @param category the category or categories of interest - * @returns A list containing the requested metadata. - */ - algorithms(category: CredentialCategory): AlgorithmInfo[]; - algorithms(category: CredentialCategory[]): AlgorithmInfo[]; - algorithms(category: CredentialCategory | CredentialCategory[]): AlgorithmInfo[] { - const categories: CredentialCategory[] = Array.isArray(category) ? category : [category]; - - const algorithms = categories - .flatMap((c) => CredentialCategories[c] as CredentialAlgorithm[]) - .map((id) => this.algorithm(id)) - .filter((info) => info !== null); - - const forwarders = Object.keys(Integrations) - .map((key: keyof typeof Integrations) => { - const forwarder: ForwarderIntegration = { forwarder: Integrations[key].id }; - return this.algorithm(forwarder); - }) - .filter((forwarder) => categories.includes(forwarder.category)); - - return algorithms.concat(forwarders); - } - - /** Look up the metadata for a specific generator algorithm - * @param id identifies the algorithm - * @returns the requested metadata, or `null` if the metadata wasn't found. - */ - algorithm(id: CredentialAlgorithm): AlgorithmInfo { - let generator: CredentialGeneratorInfo = null; - let integration: IntegrationMetadata = null; - - if (isForwarderIntegration(id)) { - const forwarderConfig = getForwarderConfiguration(id.forwarder); - integration = forwarderConfig; - - if (forwarderConfig) { - generator = toCredentialGeneratorConfiguration(forwarderConfig); - } - } else { - generator = Generators[id]; - } - - if (!generator) { - throw new Error(`Invalid credential algorithm: ${JSON.stringify(id)}`); - } - - const info: AlgorithmInfo = { - id: generator.id, - category: generator.category, - name: integration ? integration.name : this.i18nService.t(generator.nameKey), - generate: this.i18nService.t(generator.generateKey), - onGeneratedMessage: this.i18nService.t(generator.onGeneratedMessageKey), - credentialType: this.i18nService.t(generator.credentialTypeKey), - copy: this.i18nService.t(generator.copyKey), - useGeneratedValue: this.i18nService.t(generator.useGeneratedValueKey), - onlyOnRequest: generator.onlyOnRequest, - request: generator.request, - }; - - if (generator.descriptionKey) { - info.description = this.i18nService.t(generator.descriptionKey); - } - - return info; - } - - /** Get the settings for the provided configuration - * @param configuration determines which generator's settings are loaded - * @param dependencies.account$ identifies the account to which the settings are bound. - * @returns an observable that emits settings - * @remarks the observable enforces policies on the settings - */ - settings$( - configuration: Configuration, - dependencies: BoundDependency<"account", Account>, - ) { - const constraints$ = this.policy$(configuration, dependencies); - - const settings = new UserStateSubject(configuration.settings.account, this.providers, { - constraints$, - account$: dependencies.account$, - }); - - const settings$ = settings.pipe( - map((settings) => settings ?? structuredClone(configuration.settings.initial)), - ); - - return settings$; - } - - /** Get a subject bound to credential generator preferences. - * @param dependencies.account$ identifies the account to which the preferences are bound - * @returns a subject bound to the user's preferences - * @remarks Preferences determine which algorithms are used when generating a - * credential from a credential category (e.g. `PassX` or `Username`). Preferences - * should not be used to hold navigation history. Use @bitwarden/generator-navigation - * instead. - */ - preferences( - dependencies: BoundDependency<"account", Account>, - ): UserStateSubject { - // FIXME: enforce policy - const subject = new UserStateSubject(PREFERENCES, this.providers, dependencies); - - return subject; - } - - /** Get a subject bound to a specific user's settings - * @param configuration determines which generator's settings are loaded - * @param dependencies.account$ identifies the account to which the settings are bound - * @returns a subject bound to the requested user's generator settings - * @remarks the subject enforces policy for the settings - */ - settings( - configuration: Readonly>, - dependencies: BoundDependency<"account", Account>, - ) { - const constraints$ = this.policy$(configuration, dependencies); - - const subject = new UserStateSubject(configuration.settings.account, this.providers, { - constraints$, - account$: dependencies.account$, - }); - - return subject; - } - - /** Get the policy constraints for the provided configuration - * @param dependencies.account$ determines which user's policy is loaded - * @returns an observable that emits the policy once `dependencies.account$` - * and the policy become available. - */ - policy$( - configuration: Configuration, - dependencies: BoundDependency<"account", Account>, - ): Observable> { - const constraints$ = dependencies.account$.pipe( - map((account) => { - if (account.emailVerified) { - return { userId: account.id, email: account.email }; - } - - return { userId: account.id, email: null }; - }), - switchMap(({ userId, email }) => { - // complete policy emissions otherwise `switchMap` holds `policies$` open indefinitely - const policies$ = this.policyService - .policiesByType$(configuration.policy.type, userId) - .pipe( - mapPolicyToConstraints(configuration.policy, email), - takeUntil(anyComplete(dependencies.account$)), - ); - return policies$; - }), - ); - - return constraints$; - } -} diff --git a/libs/tools/generator/core/src/services/credential-preferences.spec.ts b/libs/tools/generator/core/src/services/credential-preferences.spec.ts deleted file mode 100644 index fc7c3e1bbc6..00000000000 --- a/libs/tools/generator/core/src/services/credential-preferences.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { DefaultCredentialPreferences } from "../data"; - -import { PREFERENCES } from "./credential-preferences"; - -describe("PREFERENCES", () => { - describe("deserializer", () => { - it.each([[null], [undefined]])("creates new preferences (= %p)", (value) => { - const result = PREFERENCES.deserializer(value); - - expect(result).toEqual(DefaultCredentialPreferences); - }); - - it("fills missing password preferences", () => { - const input = { ...DefaultCredentialPreferences }; - delete input.password; - - const result = PREFERENCES.deserializer(input as any); - - expect(result).toEqual(DefaultCredentialPreferences); - }); - - it("fills missing email preferences", () => { - const input = { ...DefaultCredentialPreferences }; - delete input.email; - - const result = PREFERENCES.deserializer(input as any); - - expect(result).toEqual(DefaultCredentialPreferences); - }); - - it("fills missing username preferences", () => { - const input = { ...DefaultCredentialPreferences }; - delete input.username; - - const result = PREFERENCES.deserializer(input as any); - - expect(result).toEqual(DefaultCredentialPreferences); - }); - - it("converts updated fields to Dates", () => { - const input = structuredClone(DefaultCredentialPreferences); - input.email.updated = "1970-01-01T00:00:00.100Z" as any; - input.password.updated = "1970-01-01T00:00:00.200Z" as any; - input.username.updated = "1970-01-01T00:00:00.300Z" as any; - - const result = PREFERENCES.deserializer(input as any); - - expect(result.email.updated).toEqual(new Date(100)); - expect(result.password.updated).toEqual(new Date(200)); - expect(result.username.updated).toEqual(new Date(300)); - }); - }); -}); diff --git a/libs/tools/generator/core/src/services/credential-preferences.ts b/libs/tools/generator/core/src/services/credential-preferences.ts deleted file mode 100644 index 3f6a6c1e1bd..00000000000 --- a/libs/tools/generator/core/src/services/credential-preferences.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; - -import { DefaultCredentialPreferences } from "../data"; -import { CredentialPreference } from "../types"; - -/** plaintext password generation options */ -export const PREFERENCES = new UserKeyDefinition( - GENERATOR_DISK, - "credentialPreferences", - { - deserializer: (value) => { - const result = (value as any) ?? {}; - - for (const key in DefaultCredentialPreferences) { - // bind `key` to `category` to transmute the type - const category: keyof typeof DefaultCredentialPreferences = key as any; - - const preference = result[category] ?? { ...DefaultCredentialPreferences[category] }; - if (typeof preference.updated === "string") { - preference.updated = new Date(preference.updated); - } - - result[category] = preference; - } - - return result; - }, - clearOn: ["logout"], - }, -); diff --git a/libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts b/libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts new file mode 100644 index 00000000000..81e7ae6ac63 --- /dev/null +++ b/libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts @@ -0,0 +1,356 @@ +import { BehaviorSubject, Subject, firstValueFrom, of } from "rxjs"; + +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; +import { Site, VendorId } from "@bitwarden/common/tools/extension"; +import { Bitwarden } from "@bitwarden/common/tools/extension/vendor/bitwarden"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; +import { SemanticLogger, ifEnabledSemanticLoggerProvider } from "@bitwarden/common/tools/log"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { awaitAsync } from "../../../../../common/spec"; +import { + Algorithm, + CredentialAlgorithm, + CredentialType, + ForwarderExtensionId, + GeneratorMetadata, + Profile, + Type, +} from "../metadata"; +import { CredentialGeneratorProviders } from "../providers"; +import { GenerateRequest, GeneratedCredential } from "../types"; + +import { DefaultCredentialGeneratorService } from "./default-credential-generator.service"; + +// Custom type for jest.fn() mocks to preserve their type +type JestMockFunction any> = jest.Mock, Parameters>; + +// two-level partial that preserves jest.fn() mock types +type MockTwoLevelPartial = { + [K in keyof T]?: T[K] extends object + ? { + [P in keyof T[K]]?: T[K][P] extends (...args: any) => any + ? JestMockFunction + : T[K][P]; + } + : T[K]; +}; + +describe("DefaultCredentialGeneratorService", () => { + let service: DefaultCredentialGeneratorService; + let providers: MockTwoLevelPartial; + let system: any; + let log: SemanticLogger; + let mockExtension: { settings: jest.Mock }; + let account: Account; + let createService: (overrides?: any) => DefaultCredentialGeneratorService; + + beforeEach(() => { + log = ifEnabledSemanticLoggerProvider(false, new ConsoleLogService(true), { + from: "DefaultCredentialGeneratorService tests", + }); + + mockExtension = { settings: jest.fn() }; + + // Use a hard-coded value for mockAccount + account = { + id: "test-account-id" as UserId, + emailVerified: true, + email: "test@example.com", + name: "Test User", + }; + + system = { + log: jest.fn().mockReturnValue(log), + extension: mockExtension, + }; + + providers = { + metadata: { + metadata: jest.fn(), + preference$: jest.fn(), + algorithms$: jest.fn(), + algorithms: jest.fn(), + preferences: jest.fn(), + }, + profile: { + settings: jest.fn(), + constraints$: jest.fn(), + }, + generator: {}, + }; + + // Creating the service instance with a cast to the expected type + createService = (overrides = {}) => { + // Force cast the incomplete providers to the required type + // similar to how the overrides are applied + const providersCast = providers as unknown as CredentialGeneratorProviders; + + const instance = new DefaultCredentialGeneratorService(providersCast, system); + Object.assign(instance, overrides); + return instance; + }; + + service = createService(); + }); + + describe("generate$", () => { + it("should generate credentials when provided a specific algorithm", async () => { + const mockEngine = { + generate: jest + .fn() + .mockReturnValue( + of( + new GeneratedCredential("generatedPassword", Type.password, Date.now(), "unit test"), + ), + ), + }; + const mockMetadata = { + id: Algorithm.password, + engine: { create: jest.fn().mockReturnValue(mockEngine) }, + } as unknown as GeneratorMetadata; + const mockSettings = new BehaviorSubject({ length: 12 }); + providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata); + service = createService({ + settings: () => mockSettings as any, + }); + const on$ = new Subject(); + const account$ = new BehaviorSubject(account); + const result$ = new BehaviorSubject(null); + + service.generate$({ on$, account$ }).subscribe(result$); + on$.next({ algorithm: Algorithm.password }); + await awaitAsync(); + + expect(result$.value?.credential).toEqual("generatedPassword"); + expect(providers.metadata!.metadata).toHaveBeenCalledWith(Algorithm.password); + expect(mockMetadata.engine.create).toHaveBeenCalled(); + expect(mockEngine.generate).toHaveBeenCalled(); + }); + + it("should determine preferred algorithm from credential type and generate credentials", async () => { + const mockEngine = { + generate: jest + .fn() + .mockReturnValue( + of(new GeneratedCredential("generatedPassword", "password", Date.now(), "unit test")), + ), + }; + const mockMetadata = { + id: "testAlgorithm", + engine: { create: jest.fn().mockReturnValue(mockEngine) }, + } as unknown as GeneratorMetadata; + const mockSettings = new BehaviorSubject({ length: 12 }); + + providers.metadata!.preference$ = jest + .fn() + .mockReturnValue(of("testAlgorithm" as CredentialAlgorithm)); + providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata); + service = createService({ + settings: () => mockSettings as any, + }); + + const on$ = new Subject(); + const account$ = new BehaviorSubject(account); + const result$ = new BehaviorSubject(null); + + service.generate$({ on$, account$ }).subscribe(result$); + on$.next({ type: Type.password }); + await awaitAsync(); + + expect(result$.value?.credential).toBe("generatedPassword"); + expect(result$.value?.category).toBe(Type.password); + expect(providers.metadata!.metadata).toHaveBeenCalledWith("testAlgorithm"); + }); + }); + + describe("algorithms$", () => { + it("should retrieve and map available algorithms for a credential type", async () => { + const mockAlgorithms = [Algorithm.password, Algorithm.passphrase] as CredentialAlgorithm[]; + const mockMetadata1 = { id: Algorithm.password } as GeneratorMetadata; + const mockMetadata2 = { id: Algorithm.passphrase } as GeneratorMetadata; + + providers.metadata!.algorithms$ = jest.fn().mockReturnValue(of(mockAlgorithms)); + providers.metadata!.metadata = jest + .fn() + .mockReturnValueOnce(mockMetadata1) + .mockReturnValueOnce(mockMetadata2); + + const result = await firstValueFrom( + service.algorithms$("password" as CredentialType, { account$: of(account) }), + ); + + expect(result).toEqual([mockMetadata1, mockMetadata2]); + }); + }); + + describe("algorithms", () => { + it("should list algorithm metadata for a single credential type", () => { + providers.metadata!.algorithms = jest + .fn() + .mockReturnValue([Algorithm.password, Algorithm.passphrase] as CredentialAlgorithm[]); + service = createService({ + algorithm: (id: CredentialAlgorithm) => ({ id }) as GeneratorMetadata, + }); + + const result = service.algorithms("password" as CredentialType); + + expect(result).toEqual([{ id: Algorithm.password }, { id: Algorithm.passphrase }]); + expect(providers.metadata!.algorithms).toHaveBeenCalledWith({ type: "password" }); + }); + + it("should list combined algorithm metadata for multiple credential types", () => { + providers.metadata!.algorithms = jest + .fn() + .mockReturnValueOnce([Algorithm.password] as CredentialAlgorithm[]) + .mockReturnValueOnce([Algorithm.username] as CredentialAlgorithm[]); + + service = createService({ + algorithm: (id: CredentialAlgorithm) => ({ id }) as GeneratorMetadata, + }); + + const result = service.algorithms(["password", "username"] as CredentialType[]); + + expect(result).toEqual([{ id: Algorithm.password }, { id: Algorithm.username }]); + expect(providers.metadata!.algorithms).toHaveBeenCalledWith({ type: "password" }); + expect(providers.metadata!.algorithms).toHaveBeenCalledWith({ type: "username" }); + }); + }); + + describe("algorithm", () => { + it("should retrieve metadata for a specific generator algorithm", () => { + const mockMetadata = { id: Algorithm.password } as GeneratorMetadata; + providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata); + + const result = service.algorithm(Algorithm.password); + + expect(result).toBe(mockMetadata); + expect(providers.metadata!.metadata).toHaveBeenCalledWith(Algorithm.password); + }); + + it("should log a panic when algorithm ID is invalid", () => { + providers.metadata!.metadata = jest.fn().mockReturnValue(null); + + expect(() => service.algorithm("invalidAlgo" as CredentialAlgorithm)).toThrow( + "invalid credential algorithm", + ); + }); + }); + + describe("forwarder", () => { + it("should retrieve forwarder metadata for a specific vendor", () => { + const vendorId = Vendor.bitwarden; + const forwarderExtensionId: ForwarderExtensionId = { forwarder: vendorId }; + const mockMetadata = { + id: forwarderExtensionId, + type: "email" as CredentialType, + } as GeneratorMetadata; + + providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata); + + const result = service.forwarder(vendorId); + + expect(result).toBe(mockMetadata); + expect(providers.metadata!.metadata).toHaveBeenCalledWith(forwarderExtensionId); + }); + + it("should log a panic when vendor ID is invalid", () => { + const invalidVendorId = "invalid-vendor" as VendorId; + providers.metadata!.metadata = jest.fn().mockReturnValue(null); + + expect(() => service.forwarder(invalidVendorId)).toThrow("invalid vendor"); + }); + }); + + describe("preferences", () => { + it("should retrieve credential preferences bound to the user's account", () => { + const mockPreferences = { defaultType: "password" }; + providers.metadata!.preferences = jest.fn().mockReturnValue(mockPreferences); + + const result = service.preferences({ account$: of(account) }); + + expect(result).toBe(mockPreferences); + }); + }); + + describe("settings", () => { + it("should load user settings for account-bound profiles", () => { + const mockSettings = { value: { length: 12 } }; + const mockMetadata = { + id: "test", + profiles: { + [Profile.account]: { id: "accountProfile" }, + }, + } as unknown as GeneratorMetadata; + + providers.profile!.settings = jest.fn().mockReturnValue(mockSettings); + + const result = service.settings(mockMetadata, { account$: of(account) }); + + expect(result).toBe(mockSettings); + }); + + it("should load user settings for extension-bound profiles", () => { + const mockSettings = new BehaviorSubject({ value: { length: 12 } }); + const vendorId = Vendor.bitwarden; + const forwarderProfile = { + id: { forwarder: Bitwarden.id }, + site: Site.forwarder, + type: "extension", + }; + const mockMetadata = { + id: { forwarder: vendorId } as ForwarderExtensionId, + profiles: { + [Profile.account]: forwarderProfile, + }, + } as unknown as GeneratorMetadata; + + mockExtension.settings.mockReturnValue(mockSettings); + + const result = service.settings(mockMetadata, { account$: of(account) }); + + expect(result).toBe(mockSettings); + }); + + it("should log a panic when profile metadata is not found", () => { + const mockMetadata = { + id: "test", + profiles: {}, + } as unknown as GeneratorMetadata; + + expect(() => service.settings(mockMetadata, { account$: of(account) })).toThrow( + "failed to load settings; profile metadata not found", + ); + }); + }); + + describe("policy$", () => { + it("should retrieve policy constraints for a specific profile", async () => { + const mockConstraints = { minLength: 8 }; + const mockMetadata = { + id: "test", + profiles: { + [Profile.account]: { id: "accountProfile" }, + }, + } as unknown as GeneratorMetadata; + + providers.profile!.constraints$ = jest.fn().mockReturnValue(of(mockConstraints)); + + const result = await firstValueFrom(service.policy$(mockMetadata, { account$: of(account) })); + + expect(result).toEqual(mockConstraints); + }); + + it("should log a panic when profile metadata is not found for policy retrieval", () => { + const mockMetadata = { + id: "test", + profiles: {}, + } as unknown as GeneratorMetadata; + + expect(() => service.policy$(mockMetadata, { account$: of(account) })).toThrow( + "failed to load policy; profile metadata not found", + ); + }); + }); +}); diff --git a/libs/tools/generator/core/src/services/default-credential-generator.service.ts b/libs/tools/generator/core/src/services/default-credential-generator.service.ts new file mode 100644 index 00000000000..453139d284c --- /dev/null +++ b/libs/tools/generator/core/src/services/default-credential-generator.service.ts @@ -0,0 +1,219 @@ +import { + ReplaySubject, + concatMap, + filter, + first, + map, + of, + share, + shareReplay, + switchAll, + switchMap, + takeUntil, + tap, + timer, + zip, +} from "rxjs"; + +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { BoundDependency, OnDependency } from "@bitwarden/common/tools/dependencies"; +import { VendorId } from "@bitwarden/common/tools/extension"; +import { SemanticLogger } from "@bitwarden/common/tools/log"; +import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; +import { anyComplete, memoizedMap } from "@bitwarden/common/tools/rx"; +import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject"; + +import { CredentialGeneratorService } from "../abstractions"; +import { + CredentialAlgorithm, + Profile, + GeneratorMetadata, + GeneratorProfile, + isForwarderProfile, + toVendorId, + CredentialType, +} from "../metadata"; +import { CredentialGeneratorProviders } from "../providers"; +import { GenerateRequest } from "../types"; +import { isAlgorithmRequest, isTypeRequest } from "../types/metadata-request"; + +const ALGORITHM_CACHE_SIZE = 10; +const THREE_MINUTES = 3 * 60 * 1000; + +export class DefaultCredentialGeneratorService implements CredentialGeneratorService { + /** Instantiate the `DefaultCredentialGeneratorService`. + * @param provide application services required by the credential generator. + * @param system low-level services required by the credential generator. + */ + constructor( + private readonly provide: CredentialGeneratorProviders, + private readonly system: SystemServiceProvider, + ) { + this.log = system.log({ type: "DefaultCredentialGeneratorService" }); + } + + private readonly log: SemanticLogger; + + generate$(dependencies: OnDependency & BoundDependency<"account", Account>) { + const request$ = dependencies.on$.pipe(shareReplay({ refCount: true, bufferSize: 1 })); + const account$ = dependencies.account$.pipe(shareReplay({ refCount: true, bufferSize: 1 })); + + // load algorithm metadata + const metadata$ = request$.pipe( + switchMap((request) => { + if (isAlgorithmRequest(request)) { + return of(request.algorithm); + } else if (isTypeRequest(request)) { + return this.provide.metadata.preference$(request.type, { account$ }).pipe(first()); + } else { + this.log.panic(request, "algorithm or category required"); + } + }), + filter((algorithm): algorithm is CredentialAlgorithm => !!algorithm), + memoizedMap((algorithm) => this.provide.metadata.metadata(algorithm), { + size: ALGORITHM_CACHE_SIZE, + }), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + + // load the active profile's settings + const settings$ = zip(request$, metadata$).pipe( + map( + ([request, metadata]) => + [{ ...request, profile: request.profile ?? Profile.account }, metadata] as const, + ), + memoizedMap( + ([request, metadata]) => { + const [profile, algorithm] = [request.profile, metadata.id]; + + // settings$ stays hot and buffers the most recent value in the cache + // for the next `request` + const settings$ = this.settings(metadata, { account$ }, profile).pipe( + tap(() => this.log.debug({ algorithm, profile }, "settings update received")), + share({ + connector: () => new ReplaySubject(1, THREE_MINUTES), + resetOnRefCountZero: () => timer(THREE_MINUTES), + }), + tap({ + subscribe: () => this.log.debug({ algorithm, profile }, "settings hot"), + complete: () => this.log.debug({ algorithm, profile }, "settings cold"), + }), + first(), + ); + + this.log.debug({ algorithm, profile }, "settings cached"); + return settings$; + }, + { key: ([request, metadata]) => `${metadata.id}:${request.profile}` }, + ), + switchAll(), + ); + + // load the algorithm's engine + const engine$ = metadata$.pipe( + memoizedMap( + (metadata) => { + const engine = metadata.engine.create(this.provide.generator); + + this.log.debug({ algorithm: metadata.id }, "engine cached"); + return engine; + }, + { size: ALGORITHM_CACHE_SIZE }, + ), + ); + + // generation proper + const generate$ = zip([request$, settings$, engine$]).pipe( + tap(([request]) => this.log.debug(request, "generating credential")), + concatMap(([request, settings, engine]) => engine.generate(request, settings)), + takeUntil(anyComplete([settings$])), + ); + + return generate$; + } + + algorithms$(type: CredentialType, dependencies: BoundDependency<"account", Account>) { + return this.provide.metadata + .algorithms$({ type }, dependencies) + .pipe(map((algorithms) => algorithms.map((a) => this.algorithm(a)))); + } + + algorithms(type: CredentialType | CredentialType[]) { + const types: CredentialType[] = Array.isArray(type) ? type : [type]; + const algorithms = types + .flatMap((type) => this.provide.metadata.algorithms({ type })) + .map((algorithm) => this.algorithm(algorithm)); + return algorithms; + } + + algorithm(id: CredentialAlgorithm) { + const metadata = this.provide.metadata.metadata(id); + if (!metadata) { + this.log.panic({ algorithm: id }, "invalid credential algorithm"); + } + + return metadata; + } + + forwarder(id: VendorId) { + const metadata = this.provide.metadata.metadata({ forwarder: id }); + if (!metadata) { + this.log.panic({ algorithm: id }, "invalid vendor"); + } + + return metadata; + } + + preferences(dependencies: BoundDependency<"account", Account>) { + return this.provide.metadata.preferences(dependencies); + } + + settings( + metadata: Readonly>, + dependencies: BoundDependency<"account", Account>, + profile: GeneratorProfile = Profile.account, + ) { + const activeProfile = metadata.profiles[profile]; + if (!activeProfile) { + this.log.panic( + { algorithm: metadata.id, profile }, + "failed to load settings; profile metadata not found", + ); + } + + let settings: UserStateSubject; + if (isForwarderProfile(activeProfile)) { + const vendor = toVendorId(metadata.id); + if (!vendor) { + this.log.panic( + { algorithm: metadata.id, profile }, + "failed to load extension profile; vendor not specified", + ); + } + + this.log.info({ profile, vendor, site: activeProfile.site }, "loading extension profile"); + settings = this.system.extension.settings(activeProfile, vendor, dependencies); + } else { + this.log.info({ profile, algorithm: metadata.id }, "loading generator profile"); + settings = this.provide.profile.settings(activeProfile, dependencies); + } + + return settings; + } + + policy$( + metadata: Readonly>, + dependencies: BoundDependency<"account", Account>, + profile: GeneratorProfile = Profile.account, + ) { + const activeProfile = metadata.profiles[profile]; + if (!activeProfile) { + this.log.panic( + { algorithm: metadata.id, profile }, + "failed to load policy; profile metadata not found", + ); + } + + return this.provide.profile.constraints$(activeProfile, dependencies); + } +} diff --git a/libs/tools/generator/core/src/services/default-generator.service.spec.ts b/libs/tools/generator/core/src/services/default-generator.service.spec.ts index eb9642a9417..a0f9342fa28 100644 --- a/libs/tools/generator/core/src/services/default-generator.service.spec.ts +++ b/libs/tools/generator/core/src/services/default-generator.service.spec.ts @@ -18,7 +18,7 @@ import { DefaultGeneratorService } from "./default-generator.service"; function mockPolicyService(config?: { state?: BehaviorSubject }) { const service = mock(); - const stateValue = config?.state ?? new BehaviorSubject([null]); + const stateValue = config?.state ?? new BehaviorSubject([]); service.policiesByType$.mockReturnValue(stateValue); return service; @@ -119,22 +119,22 @@ describe("Password generator service", () => { it("should update the evaluator when the password generator policy changes", async () => { // set up dependencies - const state = new BehaviorSubject([null]); + const state = new BehaviorSubject([]); const policy = mockPolicyService({ state }); const strategy = mockGeneratorStrategy(); const service = new DefaultGeneratorService(strategy, policy); // model responses for the observable update. The map is called multiple times, // and the array shift ensures reference equality is maintained. - const firstEvaluator = mock>(); - const secondEvaluator = mock>(); + const firstEvaluator: PolicyEvaluator = mock>(); + const secondEvaluator: PolicyEvaluator = mock>(); const evaluators = [firstEvaluator, secondEvaluator]; - strategy.toEvaluator.mockReturnValueOnce(pipe(map(() => evaluators.shift()))); + strategy.toEvaluator.mockReturnValueOnce(pipe(map(() => evaluators.shift()!))); // act const evaluator$ = service.evaluator$(SomeUser); const firstResult = await firstValueFrom(evaluator$); - state.next([null]); + state.next([]); const secondResult = await firstValueFrom(evaluator$); // assert diff --git a/libs/tools/generator/core/src/services/index.ts b/libs/tools/generator/core/src/services/index.ts index d7184f684ae..57094e28ddd 100644 --- a/libs/tools/generator/core/src/services/index.ts +++ b/libs/tools/generator/core/src/services/index.ts @@ -1,2 +1,2 @@ export { DefaultGeneratorService } from "./default-generator.service"; -export { CredentialGeneratorService } from "./credential-generator.service"; +export { DefaultCredentialGeneratorService } from "./default-credential-generator.service"; diff --git a/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts b/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts index 83ed3b0d14e..ebeacef81e8 100644 --- a/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts @@ -2,7 +2,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { StateProvider } from "@bitwarden/common/platform/state"; import { GeneratorStrategy } from "../abstractions"; -import { DefaultEffUsernameOptions, UsernameDigits } from "../data"; +import { DefaultEffUsernameOptions } from "../data"; import { UsernameRandomizer } from "../engine"; import { newDefaultEvaluator } from "../rx"; import { EffUsernameGenerationOptions, NoPolicy } from "../types"; @@ -10,6 +10,11 @@ import { observe$PerUserId, sharedStateByUserId } from "../util"; import { EFF_USERNAME_SETTINGS } from "./storage"; +const UsernameDigits = Object.freeze({ + enabled: 4, + disabled: 0, +}); + /** Strategy for creating usernames from the EFF wordlist */ export class EffUsernameGeneratorStrategy implements GeneratorStrategy diff --git a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts index 5abcea82493..ee521d753ae 100644 --- a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts +++ b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts @@ -8,7 +8,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { DefaultPassphraseGenerationOptions, Policies } from "../data"; +import { DefaultPassphraseGenerationOptions } from "../data"; import { PasswordRandomizer } from "../engine"; import { PassphraseGeneratorOptionsEvaluator } from "../policies"; @@ -20,7 +20,7 @@ const SomeUser = "some user" as UserId; describe("Passphrase generation strategy", () => { describe("toEvaluator()", () => { it("should map to the policy evaluator", async () => { - const strategy = new PassphraseGeneratorStrategy(null, null); + const strategy = new PassphraseGeneratorStrategy(null!, null!); const policy = mock({ type: PolicyType.PasswordGenerator, data: { @@ -44,13 +44,18 @@ describe("Passphrase generation strategy", () => { it.each([[[]], [null], [undefined]])( "should map `%p` to a disabled password policy evaluator", async (policies) => { - const strategy = new PassphraseGeneratorStrategy(null, null); + const strategy = new PassphraseGeneratorStrategy(null!, null!); - const evaluator$ = of(policies).pipe(strategy.toEvaluator()); + // this case tests when the type system is subverted + const evaluator$ = of(policies!).pipe(strategy.toEvaluator()); const evaluator = await firstValueFrom(evaluator$); expect(evaluator).toBeInstanceOf(PassphraseGeneratorOptionsEvaluator); - expect(evaluator.policy).toMatchObject(Policies.Passphrase.disabledValue); + expect(evaluator.policy).toMatchObject({ + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }); }, ); }); @@ -58,7 +63,7 @@ describe("Passphrase generation strategy", () => { describe("durableState", () => { it("should use password settings key", () => { const provider = mock(); - const strategy = new PassphraseGeneratorStrategy(null, provider); + const strategy = new PassphraseGeneratorStrategy(null!, provider); strategy.durableState(SomeUser); @@ -68,7 +73,7 @@ describe("Passphrase generation strategy", () => { describe("defaults$", () => { it("should return the default subaddress options", async () => { - const strategy = new PassphraseGeneratorStrategy(null, null); + const strategy = new PassphraseGeneratorStrategy(null!, null!); const result = await firstValueFrom(strategy.defaults$(SomeUser)); @@ -78,7 +83,7 @@ describe("Passphrase generation strategy", () => { describe("policy", () => { it("should use password generator policy", () => { - const strategy = new PassphraseGeneratorStrategy(null, null); + const strategy = new PassphraseGeneratorStrategy(null!, null!); expect(strategy.policy).toBe(PolicyType.PasswordGenerator); }); @@ -95,7 +100,7 @@ describe("Passphrase generation strategy", () => { }); it("should map options", async () => { - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ numWords: 6, @@ -114,7 +119,7 @@ describe("Passphrase generation strategy", () => { }); it("should default numWords", async () => { - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ capitalize: true, @@ -132,7 +137,7 @@ describe("Passphrase generation strategy", () => { }); it("should default capitalize", async () => { - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ numWords: 6, @@ -150,7 +155,7 @@ describe("Passphrase generation strategy", () => { }); it("should default includeNumber", async () => { - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ numWords: 6, @@ -168,7 +173,7 @@ describe("Passphrase generation strategy", () => { }); it("should default wordSeparator", async () => { - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ numWords: 6, diff --git a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts index 759f065b2ff..374df84a5bd 100644 --- a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts @@ -4,8 +4,9 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { StateProvider } from "@bitwarden/common/platform/state"; import { GeneratorStrategy } from "../abstractions"; -import { DefaultPassphraseGenerationOptions, Policies } from "../data"; +import { DefaultPassphraseGenerationOptions } from "../data"; import { PasswordRandomizer } from "../engine"; +import { PassphraseGeneratorOptionsEvaluator, passphraseLeastPrivilege } from "../policies"; import { mapPolicyToEvaluator } from "../rx"; import { PassphraseGenerationOptions, PassphraseGeneratorPolicy } from "../types"; import { observe$PerUserId, optionsToEffWordListRequest, sharedStateByUserId } from "../util"; @@ -30,7 +31,16 @@ export class PassphraseGeneratorStrategy defaults$ = observe$PerUserId(() => DefaultPassphraseGenerationOptions); readonly policy = PolicyType.PasswordGenerator; toEvaluator() { - return mapPolicyToEvaluator(Policies.Passphrase); + return mapPolicyToEvaluator({ + type: PolicyType.PasswordGenerator, + disabledValue: Object.freeze({ + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }), + combine: passphraseLeastPrivilege, + createEvaluator: (policy) => new PassphraseGeneratorOptionsEvaluator(policy), + }); } // algorithm diff --git a/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts index 928e0b0dc8b..94e7c16be28 100644 --- a/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts +++ b/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts @@ -8,7 +8,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { DefaultPasswordGenerationOptions, Policies } from "../data"; +import { DefaultPasswordGenerationOptions } from "../data"; import { PasswordRandomizer } from "../engine"; import { PasswordGeneratorOptionsEvaluator } from "../policies"; @@ -20,7 +20,7 @@ const SomeUser = "some user" as UserId; describe("Password generation strategy", () => { describe("toEvaluator()", () => { it("should map to a password policy evaluator", async () => { - const strategy = new PasswordGeneratorStrategy(null, null); + const strategy = new PasswordGeneratorStrategy(null!, null!); const policy = mock({ type: PolicyType.PasswordGenerator, data: { @@ -52,13 +52,22 @@ describe("Password generation strategy", () => { it.each([[[]], [null], [undefined]])( "should map `%p` to a disabled password policy evaluator", async (policies) => { - const strategy = new PasswordGeneratorStrategy(null, null); + const strategy = new PasswordGeneratorStrategy(null!, null!); - const evaluator$ = of(policies).pipe(strategy.toEvaluator()); + // this case tests when the type system is subverted + const evaluator$ = of(policies!).pipe(strategy.toEvaluator()); const evaluator = await firstValueFrom(evaluator$); expect(evaluator).toBeInstanceOf(PasswordGeneratorOptionsEvaluator); - expect(evaluator.policy).toMatchObject(Policies.Password.disabledValue); + expect(evaluator.policy).toMatchObject({ + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }); }, ); }); @@ -66,7 +75,7 @@ describe("Password generation strategy", () => { describe("durableState", () => { it("should use password settings key", () => { const provider = mock(); - const strategy = new PasswordGeneratorStrategy(null, provider); + const strategy = new PasswordGeneratorStrategy(null!, provider); strategy.durableState(SomeUser); @@ -76,7 +85,7 @@ describe("Password generation strategy", () => { describe("defaults$", () => { it("should return the default subaddress options", async () => { - const strategy = new PasswordGeneratorStrategy(null, null); + const strategy = new PasswordGeneratorStrategy(null!, null!); const result = await firstValueFrom(strategy.defaults$(SomeUser)); @@ -86,7 +95,7 @@ describe("Password generation strategy", () => { describe("policy", () => { it("should use password generator policy", () => { - const strategy = new PasswordGeneratorStrategy(null, null); + const strategy = new PasswordGeneratorStrategy(null!, null!); expect(strategy.policy).toBe(PolicyType.PasswordGenerator); }); @@ -103,7 +112,7 @@ describe("Password generation strategy", () => { }); it("should map options", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 20, @@ -130,7 +139,7 @@ describe("Password generation strategy", () => { }); it("should disable uppercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 3, @@ -157,7 +166,7 @@ describe("Password generation strategy", () => { }); it("should disable lowercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 3, @@ -184,7 +193,7 @@ describe("Password generation strategy", () => { }); it("should disable digits", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 3, @@ -211,7 +220,7 @@ describe("Password generation strategy", () => { }); it("should disable special", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 3, @@ -238,7 +247,7 @@ describe("Password generation strategy", () => { }); it("should override length with minimums", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 20, @@ -265,7 +274,7 @@ describe("Password generation strategy", () => { }); it("should default uppercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 2, @@ -291,7 +300,7 @@ describe("Password generation strategy", () => { }); it("should default lowercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -317,7 +326,7 @@ describe("Password generation strategy", () => { }); it("should default number", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -343,7 +352,7 @@ describe("Password generation strategy", () => { }); it("should default special", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -369,7 +378,7 @@ describe("Password generation strategy", () => { }); it("should default minUppercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -395,7 +404,7 @@ describe("Password generation strategy", () => { }); it("should default minLowercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -421,7 +430,7 @@ describe("Password generation strategy", () => { }); it("should default minNumber", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -447,7 +456,7 @@ describe("Password generation strategy", () => { }); it("should default minSpecial", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, diff --git a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts index 9ff8a3d88b0..1a5070901c2 100644 --- a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts @@ -2,8 +2,9 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { StateProvider } from "@bitwarden/common/platform/state"; import { GeneratorStrategy } from "../abstractions"; -import { Policies, DefaultPasswordGenerationOptions } from "../data"; +import { DefaultPasswordGenerationOptions } from "../data"; import { PasswordRandomizer } from "../engine"; +import { PasswordGeneratorOptionsEvaluator, passwordLeastPrivilege } from "../policies"; import { mapPolicyToEvaluator } from "../rx"; import { PasswordGenerationOptions, PasswordGeneratorPolicy } from "../types"; import { observe$PerUserId, optionsToRandomAsciiRequest, sharedStateByUserId } from "../util"; @@ -27,7 +28,20 @@ export class PasswordGeneratorStrategy defaults$ = observe$PerUserId(() => DefaultPasswordGenerationOptions); readonly policy = PolicyType.PasswordGenerator; toEvaluator() { - return mapPolicyToEvaluator(Policies.Password); + return mapPolicyToEvaluator({ + type: PolicyType.PasswordGenerator, + disabledValue: { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }, + combine: passwordLeastPrivilege, + createEvaluator: (policy) => new PasswordGeneratorOptionsEvaluator(policy), + }); } // algorithm diff --git a/libs/tools/generator/core/src/types/algorithm-info.ts b/libs/tools/generator/core/src/types/algorithm-info.ts new file mode 100644 index 00000000000..a3db03600b6 --- /dev/null +++ b/libs/tools/generator/core/src/types/algorithm-info.ts @@ -0,0 +1,50 @@ +import { CredentialAlgorithm, CredentialType } from "../metadata"; + +// FIXME: deprecate or delete `AlgorithmInfo` once a better translation +// strategy is identified. +export type AlgorithmInfo = { + /** Uniquely identifies the credential configuration + * @example + * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` + * // to pattern test whether the credential describes a forwarder algorithm + * const meta : CredentialGeneratorInfo = // ... + * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; + */ + id: CredentialAlgorithm; + + /** The kind of credential generated by this configuration */ + type: CredentialType; + + /** Localized algorithm name */ + name: string; + + /* Localized generate button label */ + generate: string; + + /** Localized "credential generated" informational message */ + onGeneratedMessage: string; + + /* Localized copy button label */ + copy: string; + + /* Localized dialog button label */ + useGeneratedValue: string; + + /* Localized generated value label */ + credentialType: string; + + /** Localized algorithm description */ + description?: string; + + /** When true, credential generation must be explicitly requested. + * @remarks this property is useful when credential generation + * carries side effects, such as configuring a service external + * to Bitwarden. + */ + onlyOnRequest: boolean; + + /** Well-known fields to display on the options panel or collect from the environment. + * @remarks: at present, this is only used by forwarders + */ + request: readonly string[]; +}; diff --git a/libs/tools/generator/core/src/types/credential-generator-configuration.ts b/libs/tools/generator/core/src/types/credential-generator-configuration.ts deleted file mode 100644 index 36b0f3046a9..00000000000 --- a/libs/tools/generator/core/src/types/credential-generator-configuration.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { RestClient } from "@bitwarden/common/tools/integration/rpc"; -import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; -import { Constraints } from "@bitwarden/common/tools/types"; - -import { Randomizer } from "../abstractions"; -import { CredentialAlgorithm, CredentialCategory, PolicyConfiguration } from "../types"; - -import { CredentialGenerator } from "./credential-generator"; - -export type GeneratorDependencyProvider = { - randomizer: Randomizer; - client: RestClient; - i18nService: I18nService; -}; - -export type AlgorithmInfo = { - /** Uniquely identifies the credential configuration - * @example - * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` - * // to pattern test whether the credential describes a forwarder algorithm - * const meta : CredentialGeneratorInfo = // ... - * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; - */ - id: CredentialAlgorithm; - - /** The kind of credential generated by this configuration */ - category: CredentialCategory; - - /** Localized algorithm name */ - name: string; - - /* Localized generate button label */ - generate: string; - - /** Localized "credential generated" informational message */ - onGeneratedMessage: string; - - /* Localized copy button label */ - copy: string; - - /* Localized dialog button label */ - useGeneratedValue: string; - - /* Localized generated value label */ - credentialType: string; - - /** Localized algorithm description */ - description?: string; - - /** When true, credential generation must be explicitly requested. - * @remarks this property is useful when credential generation - * carries side effects, such as configuring a service external - * to Bitwarden. - */ - onlyOnRequest: boolean; - - /** Well-known fields to display on the options panel or collect from the environment. - * @remarks: at present, this is only used by forwarders - */ - request: readonly string[]; -}; - -/** Credential generator metadata common across credential generators */ -export type CredentialGeneratorInfo = { - /** Uniquely identifies the credential configuration - * @example - * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` - * // to pattern test whether the credential describes a forwarder algorithm - * const meta : CredentialGeneratorInfo = // ... - * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; - */ - id: CredentialAlgorithm; - - /** The kind of credential generated by this configuration */ - category: CredentialCategory; - - /** Localization key for the credential name */ - nameKey: string; - - /** Localization key for the credential description*/ - descriptionKey?: string; - - /** Localization key for the generate command label */ - generateKey: string; - - /** Localization key for the copy button label */ - copyKey: string; - - /** Localization key for the "credential generated" informational message */ - onGeneratedMessageKey: string; - - /** Localized "use generated credential" button label */ - useGeneratedValueKey: string; - - /** Localization key for describing the kind of credential generated - * by this generator. - */ - credentialTypeKey: string; - - /** When true, credential generation must be explicitly requested. - * @remarks this property is useful when credential generation - * carries side effects, such as configuring a service external - * to Bitwarden. - */ - onlyOnRequest: boolean; - - /** Well-known fields to display on the options panel or collect from the environment. - * @remarks: at present, this is only used by forwarders - */ - request: readonly string[]; -}; - -/** Credential generator metadata that relies upon typed setting and policy definitions. - * @example - * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` - * // to pattern test whether the credential describes a forwarder algorithm - * const meta : CredentialGeneratorInfo = // ... - * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; - */ -export type CredentialGeneratorConfiguration = CredentialGeneratorInfo & { - /** An algorithm that generates credentials when ran. */ - engine: { - /** Factory for the generator - */ - // FIXME: note that this erases the engine's type so that credentials are - // generated uniformly. This property needs to be maintained for - // the credential generator, but engine configurations should return - // the underlying type. `create` may be able to do double-duty w/ an - // engine definition if `CredentialGenerator` can be made covariant. - create: (randomizer: GeneratorDependencyProvider) => CredentialGenerator; - }; - /** Defines the stored parameters for credential generation */ - settings: { - /** value used when an account's settings haven't been initialized - * @deprecated use `ObjectKey.initial` for your desired storage property instead - */ - initial: Readonly>; - - /** Application-global constraints that apply to account settings */ - constraints: Constraints; - - /** storage location for account-global settings */ - account: UserKeyDefinition | ObjectKey; - - /** storage location for *plaintext* settings imports */ - import?: UserKeyDefinition | ObjectKey, Settings>; - }; - - /** defines how to construct policy for this settings instance */ - policy: PolicyConfiguration; -}; diff --git a/libs/tools/generator/core/src/types/credential-preference.ts b/libs/tools/generator/core/src/types/credential-preference.ts new file mode 100644 index 00000000000..957299e5c31 --- /dev/null +++ b/libs/tools/generator/core/src/types/credential-preference.ts @@ -0,0 +1,10 @@ +import { CredentialAlgorithm, CredentialType } from "../metadata"; + +/** The kind of credential to generate using a compound configuration. */ +// FIXME: extend the preferences to include a preferred forwarder +export type CredentialPreference = { + [Key in CredentialType]: { + algorithm: CredentialAlgorithm; + updated: Date; + }; +}; diff --git a/libs/tools/generator/core/src/types/forwarder-options.ts b/libs/tools/generator/core/src/types/forwarder-options.ts index 7ba04da99a8..06084261283 100644 --- a/libs/tools/generator/core/src/types/forwarder-options.ts +++ b/libs/tools/generator/core/src/types/forwarder-options.ts @@ -7,32 +7,65 @@ import { import { EmailDomainSettings, EmailPrefixSettings } from "../engine"; +// FIXME: this type alias is in place for legacy support purposes; +// when replacing the forwarder implementation, eliminate `ForwarderId` and +// `IntegrationId`. The proper type is `VendorId`. /** Identifiers for email forwarding services. * @remarks These are used to select forwarder-specific options. * The must be kept in sync with the forwarder implementations. */ export type ForwarderId = IntegrationId; -/** Metadata format for email forwarding services. */ -export type ForwarderMetadata = { - /** The unique identifier for the forwarder. */ - id: ForwarderId; - - /** The name of the service the forwarder queries. */ - name: string; - - /** Whether the forwarder is valid for self-hosted instances of Bitwarden. */ - validForSelfHosted: boolean; -}; - -/** Options common to all forwarder APIs */ +/** Options common to all forwarder APIs + * @deprecated use {@link ForwarderOptions} instead. + */ export type ApiOptions = ApiSettings & IntegrationRequest; -/** Api configuration for forwarders that support self-hosted installations. */ +/** Api configuration for forwarders that support self-hosted installations. + * @deprecated use {@link ForwarderOptions} instead. + */ export type SelfHostedApiOptions = SelfHostedApiSettings & IntegrationRequest; -/** Api configuration for forwarders that support custom domains. */ +/** Api configuration for forwarders that support custom domains. + * @deprecated use {@link ForwarderOptions} instead. + */ export type EmailDomainOptions = EmailDomainSettings; -/** Api configuration for forwarders that support custom email parts. */ +/** Api configuration for forwarders that support custom email parts. + * @deprecated use {@link ForwarderOptions} instead. + */ export type EmailPrefixOptions = EmailDomainSettings & EmailPrefixSettings; + +/** These options are used by all forwarders; each forwarder uses a different set, + * as defined by `GeneratorMetadata.capabilities.fields`. + */ +export type ForwarderOptions = Partial< + { + /** bearer token that authenticates bitwarden to the forwarder. + * This is required to issue an API request. + */ + token: string; + + /** The base URL of the forwarder's API. + * When this is undefined or empty, the forwarder's default production API is used. + */ + baseUrl: string; + + /** The domain part of the generated email address. + * @remarks The domain should be authorized by the forwarder before + * submitting a request through bitwarden. + * @example If the domain is `domain.io` and the generated username + * is `jd`, then the generated email address will be `jd@domain.io` + */ + domain: string; + + /** A prefix joined to the generated email address' username. + * @example If the prefix is `foo`, the generated username is `bar`, + * and the domain is `domain.io`, then the generated email address is + * `foobar@domain.io`. + */ + prefix: string; + } & EmailDomainSettings & + EmailPrefixSettings & + SelfHostedApiSettings +>; diff --git a/libs/tools/generator/core/src/types/generate-request.ts b/libs/tools/generator/core/src/types/generate-request.ts index c7d5bf9c41c..9dbaaee12f6 100644 --- a/libs/tools/generator/core/src/types/generate-request.ts +++ b/libs/tools/generator/core/src/types/generate-request.ts @@ -1,6 +1,15 @@ +import { RequireExactlyOne } from "type-fest"; + +import { CredentialType, GeneratorProfile, CredentialAlgorithm } from "../metadata"; + /** Contextual information about the application state when a generator is invoked. */ -export type GenerateRequest = { +export type GenerateRequest = RequireExactlyOne< + { type: CredentialType; algorithm: CredentialAlgorithm }, + "type" | "algorithm" +> & { + profile?: GeneratorProfile; + /** Traces the origin of the generation request. This parameter is * copied to the generated credential. * diff --git a/libs/tools/generator/core/src/types/generated-credential.spec.ts b/libs/tools/generator/core/src/types/generated-credential.spec.ts index 6a498282fe3..3d8d2c9bd4d 100644 --- a/libs/tools/generator/core/src/types/generated-credential.spec.ts +++ b/libs/tools/generator/core/src/types/generated-credential.spec.ts @@ -1,40 +1,42 @@ -import { CredentialAlgorithm, GeneratedCredential } from "."; +import { Type } from "../metadata"; + +import { GeneratedCredential } from "./generated-credential"; describe("GeneratedCredential", () => { describe("constructor", () => { it("assigns credential", () => { - const result = new GeneratedCredential("example", "passphrase", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); expect(result.credential).toEqual("example"); }); it("assigns category", () => { - const result = new GeneratedCredential("example", "passphrase", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); - expect(result.category).toEqual("passphrase"); + expect(result.category).toEqual(Type.password); }); it("passes through date parameters", () => { - const result = new GeneratedCredential("example", "password", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); expect(result.generationDate).toEqual(new Date(100)); }); it("converts numeric dates to Dates", () => { - const result = new GeneratedCredential("example", "password", 100); + const result = new GeneratedCredential("example", Type.password, 100); expect(result.generationDate).toEqual(new Date(100)); }); }); it("toJSON converts from a credential into a JSON object", () => { - const credential = new GeneratedCredential("example", "password", new Date(100)); + const credential = new GeneratedCredential("example", Type.password, new Date(100)); const result = credential.toJSON(); expect(result).toEqual({ credential: "example", - category: "password" as CredentialAlgorithm, + category: Type.password, generationDate: 100, }); }); @@ -42,7 +44,7 @@ describe("GeneratedCredential", () => { it("fromJSON converts Json objects into credentials", () => { const jsonValue = { credential: "example", - category: "password" as CredentialAlgorithm, + category: Type.password, generationDate: 100, }; diff --git a/libs/tools/generator/core/src/types/generated-credential.ts b/libs/tools/generator/core/src/types/generated-credential.ts index 99b864b9fd8..695e3866920 100644 --- a/libs/tools/generator/core/src/types/generated-credential.ts +++ b/libs/tools/generator/core/src/types/generated-credential.ts @@ -1,13 +1,13 @@ import { Jsonify } from "type-fest"; -import { CredentialAlgorithm } from "./generator-type"; +import { CredentialType } from "../metadata"; /** A credential generation result */ export class GeneratedCredential { /** * Instantiates a generated credential * @param credential The value of the generated credential (e.g. a password) - * @param category The kind of credential + * @param category The type of credential * @param generationDate The date that the credential was generated. * Numeric values should are interpreted using {@link Date.valueOf} * semantics. @@ -16,7 +16,9 @@ export class GeneratedCredential { */ constructor( readonly credential: string, - readonly category: CredentialAlgorithm, + // FIXME: create a way to migrate the data stored in `category` to a new `type` + // field. The hard part: This requires the migration occur post-decryption. + readonly category: CredentialType, generationDate: Date | number, readonly source?: string, readonly website?: string, diff --git a/libs/tools/generator/core/src/types/generator-type.ts b/libs/tools/generator/core/src/types/generator-type.ts deleted file mode 100644 index c75e4329610..00000000000 --- a/libs/tools/generator/core/src/types/generator-type.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { VendorId } from "@bitwarden/common/tools/extension"; -import { IntegrationId } from "@bitwarden/common/tools/integration"; - -import { EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "../data/generator-types"; -import { AlgorithmsByType, CredentialType } from "../metadata"; - -/** A type of password that may be generated by the credential generator. */ -export type PasswordAlgorithm = (typeof PasswordAlgorithms)[number]; - -/** A type of username that may be generated by the credential generator. */ -export type UsernameAlgorithm = (typeof UsernameAlgorithms)[number]; - -/** A type of email address that may be generated by the credential generator. */ -export type EmailAlgorithm = (typeof EmailAlgorithms)[number]; - -export type ForwarderIntegration = { forwarder: IntegrationId & VendorId }; - -/** Returns true when the input algorithm is a forwarder integration. */ -export function isForwarderIntegration( - algorithm: CredentialAlgorithm, -): algorithm is ForwarderIntegration { - return algorithm && typeof algorithm === "object" && "forwarder" in algorithm; -} - -export function isSameAlgorithm(lhs: CredentialAlgorithm, rhs: CredentialAlgorithm) { - if (lhs === rhs) { - return true; - } else if (isForwarderIntegration(lhs) && isForwarderIntegration(rhs)) { - return lhs.forwarder === rhs.forwarder; - } else { - return false; - } -} - -/** A type of credential that may be generated by the credential generator. */ -export type CredentialAlgorithm = - | PasswordAlgorithm - | UsernameAlgorithm - | EmailAlgorithm - | ForwarderIntegration; - -/** Compound credential types supported by the credential generator. */ -export const CredentialCategories = Object.freeze({ - /** Lists algorithms in the "password" credential category */ - password: PasswordAlgorithms as Readonly, - - /** Lists algorithms in the "username" credential category */ - username: UsernameAlgorithms as Readonly, - - /** Lists algorithms in the "email" credential category */ - email: EmailAlgorithms as Readonly<(EmailAlgorithm | ForwarderIntegration)[]>, -}); - -/** Returns true when the input algorithm is a password algorithm. */ -export function isPasswordAlgorithm( - algorithm: CredentialAlgorithm, -): algorithm is PasswordAlgorithm { - return PasswordAlgorithms.includes(algorithm as any); -} - -/** Returns true when the input algorithm is a username algorithm. */ -export function isUsernameAlgorithm( - algorithm: CredentialAlgorithm, -): algorithm is UsernameAlgorithm { - return UsernameAlgorithms.includes(algorithm as any); -} - -/** Returns true when the input algorithm is an email algorithm. */ -export function isEmailAlgorithm(algorithm: CredentialAlgorithm): algorithm is EmailAlgorithm { - return EmailAlgorithms.includes(algorithm as any) || isForwarderIntegration(algorithm); -} - -/** A type of compound credential that may be generated by the credential generator. */ -export type CredentialCategory = keyof typeof CredentialCategories; - -/** The kind of credential to generate using a compound configuration. */ -// FIXME: extend the preferences to include a preferred forwarder -export type CredentialPreference = { - [Key in CredentialType & CredentialCategory]: { - algorithm: CredentialAlgorithm & (typeof AlgorithmsByType)[Key][number]; - updated: Date; - }; -}; diff --git a/libs/tools/generator/core/src/types/index.ts b/libs/tools/generator/core/src/types/index.ts index 3e392257b0c..f6460342b09 100644 --- a/libs/tools/generator/core/src/types/index.ts +++ b/libs/tools/generator/core/src/types/index.ts @@ -1,16 +1,14 @@ -import { EmailAlgorithm, PasswordAlgorithm, UsernameAlgorithm } from "./generator-type"; - export * from "./boundary"; export * from "./catchall-generator-options"; export * from "./credential-generator"; -export * from "./credential-generator-configuration"; +export * from "./algorithm-info"; export * from "./eff-username-generator-options"; export * from "./forwarder-options"; export * from "./generate-request"; export * from "./generator-constraints"; export * from "./generated-credential"; export * from "./generator-options"; -export * from "./generator-type"; +export * from "./credential-preference"; export * from "./no-policy"; export * from "./passphrase-generation-options"; export * from "./passphrase-generator-policy"; @@ -19,13 +17,3 @@ export * from "./password-generator-policy"; export * from "./policy-configuration"; export * from "./subaddress-generator-options"; export * from "./word-options"; - -/** Provided for backwards compatibility only. - * @deprecated Use one of the Algorithm types instead. - */ -export type GeneratorType = PasswordAlgorithm | UsernameAlgorithm | EmailAlgorithm; - -/** Provided for backwards compatibility only. - * @deprecated Use one of the Algorithm types instead. - */ -export type PasswordType = PasswordAlgorithm; diff --git a/libs/tools/generator/core/src/types/policy-configuration.ts b/libs/tools/generator/core/src/types/policy-configuration.ts index 07ded886609..e4db437575a 100644 --- a/libs/tools/generator/core/src/types/policy-configuration.ts +++ b/libs/tools/generator/core/src/types/policy-configuration.ts @@ -3,8 +3,6 @@ import { Policy as AdminPolicy } from "@bitwarden/common/admin-console/models/do import { PolicyEvaluator } from "../abstractions"; -import { GeneratorConstraints } from "./generator-constraints"; - /** Determines how to construct a password generator policy */ export type PolicyConfiguration = { type: PolicyType; @@ -22,15 +20,4 @@ export type PolicyConfiguration = { * Use `toConstraints` instead. */ createEvaluator: (policy: Policy) => PolicyEvaluator; - - /** Converts policy service data into actionable policy constraints. - * - * @param policy - the policy to map into policy constraints. - * @param email - the default email to extend. - * - * @remarks this version includes constraints needed for the reactive forms; - * it was introduced so that the constraints can be incrementally introduced - * as the new UI is built. - */ - toConstraints: (policy: Policy, email: string) => GeneratorConstraints; }; diff --git a/libs/tools/generator/core/src/util.spec.ts b/libs/tools/generator/core/src/util.spec.ts index 8ed95a9f268..5aeaeb3c67e 100644 --- a/libs/tools/generator/core/src/util.spec.ts +++ b/libs/tools/generator/core/src/util.spec.ts @@ -1,5 +1,6 @@ import { DefaultPassphraseGenerationOptions } from "./data"; -import { optionsToEffWordListRequest, optionsToRandomAsciiRequest, sum } from "./util"; +import { GeneratorConstraints, PassphraseGenerationOptions } from "./types"; +import { optionsToEffWordListRequest, optionsToRandomAsciiRequest, sum, equivalent } from "./util"; describe("sum", () => { it("returns 0 when the list is empty", () => { @@ -424,3 +425,90 @@ describe("optionsToEffWordListRequest", () => { }); }); }); + +describe("equivalent", () => { + // constructs a partial constraints object; only the properties compared + // by `equivalent` are included. + function createConstraints( + policyInEffect: boolean, + numWordsMin?: number, + capitalize?: boolean, + ): GeneratorConstraints { + return { + constraints: { + policyInEffect, + numWords: numWordsMin !== undefined ? { min: numWordsMin } : undefined, + capitalize: capitalize !== undefined ? { requiredValue: capitalize } : undefined, + }, + } as unknown as GeneratorConstraints; + } + + it("should return true for identical constraints", () => { + const lhs = createConstraints(false, 3, true); + const rhs = createConstraints(false, 3, true); + + expect(equivalent(lhs, rhs)).toBe(true); + }); + + it("should return false when policy effects differ", () => { + const lhs = createConstraints(true, 3); + const rhs = createConstraints(false, 3); + + expect(equivalent(lhs, rhs)).toBe(false); + }); + + it("should return false when constraint values differ", () => { + const lhs = createConstraints(false, 3); + const rhs = createConstraints(false, 4); + + expect(equivalent(lhs, rhs)).toBe(false); + }); + + it("should return false when one has additional constraints", () => { + const lhs = createConstraints(false, 3, true); + const rhs = createConstraints(false, 3); + + expect(equivalent(lhs, rhs)).toBe(false); + }); + + it("should handle undefined constraints", () => { + const lhs = createConstraints(false); + const rhs = createConstraints(false); + + expect(equivalent(lhs, rhs)).toBe(true); + }); + + it("should handle empty constraint objects", () => { + const lhs = { + constraints: { + policyInEffect: false, + numWords: {}, + }, + } as unknown as GeneratorConstraints; + const rhs = { + constraints: { + policyInEffect: false, + numWords: {}, + }, + } as unknown as GeneratorConstraints; + + expect(equivalent(lhs, rhs)).toBe(true); + }); + + it("should return false when inner constraint properties differ", () => { + const lhs = { + constraints: { + policyInEffect: false, + numWords: { min: 3, max: 5 }, + }, + } as any; + const rhs = { + constraints: { + policyInEffect: false, + numWords: { min: 3, max: 6 }, + }, + } as unknown as GeneratorConstraints; + + expect(equivalent(lhs, rhs)).toBe(false); + }); +}); diff --git a/libs/tools/generator/core/src/util.ts b/libs/tools/generator/core/src/util.ts index 4b6041ffeba..62f749faf56 100644 --- a/libs/tools/generator/core/src/util.ts +++ b/libs/tools/generator/core/src/util.ts @@ -14,7 +14,11 @@ import { DefaultPassphraseGenerationOptions, DefaultPasswordGenerationOptions, } from "./data"; -import { PassphraseGenerationOptions, PasswordGenerationOptions } from "./types"; +import { + PassphraseGenerationOptions, + PasswordGenerationOptions, + GeneratorConstraints, +} from "./types"; /** construct a method that outputs a copy of `defaultValue` as an observable. */ export function observe$PerUserId( @@ -135,3 +139,30 @@ export function optionsToEffWordListRequest(options: PassphraseGenerationOptions return request; } + +export function equivalent(lhs: GeneratorConstraints, rhs: GeneratorConstraints): boolean { + if (lhs.constraints.policyInEffect !== rhs.constraints.policyInEffect) { + return false; + } + + // safe because `Constraints` shares keys with `T` + const keys = Object.keys(lhs.constraints) as (keyof T)[]; + + // use `for` loop so that `equivalent` can return as soon as the constraints + // differ. Using `array.xyz` would evaluate the whole key list eagerly + for (const k of keys) { + if (!(k in rhs.constraints)) { + return false; + } + + const lhsConstraints: any = lhs.constraints[k] ?? {}; + const rhsConstraints: any = rhs.constraints[k] ?? {}; + + const innerKeys = Object.keys(lhsConstraints); + if (innerKeys.some((k) => lhsConstraints[k] !== rhsConstraints[k])) { + return false; + } + } + + return true; +} diff --git a/libs/tools/generator/core/tsconfig.json b/libs/tools/generator/core/tsconfig.json index a95b588686f..303f913ba23 100644 --- a/libs/tools/generator/core/tsconfig.json +++ b/libs/tools/generator/core/tsconfig.json @@ -8,10 +8,6 @@ "@bitwarden/key-management": ["../../../key-management/src"] } }, - "include": [ - "src", - "../extensions/src/history/generator-history.abstraction.ts", - "../extensions/src/navigation/generator-navigation.service.abstraction.ts" - ], + "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/extensions/history/src/generated-credential.spec.ts b/libs/tools/generator/extensions/history/src/generated-credential.spec.ts index 846f2ee96be..26a48cb83ea 100644 --- a/libs/tools/generator/extensions/history/src/generated-credential.spec.ts +++ b/libs/tools/generator/extensions/history/src/generated-credential.spec.ts @@ -1,40 +1,42 @@ -import { GeneratorCategory, GeneratedCredential } from "."; +import { Type } from "@bitwarden/generator-core"; + +import { GeneratedCredential } from "."; describe("GeneratedCredential", () => { describe("constructor", () => { it("assigns credential", () => { - const result = new GeneratedCredential("example", "passphrase", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); expect(result.credential).toEqual("example"); }); it("assigns category", () => { - const result = new GeneratedCredential("example", "passphrase", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); - expect(result.category).toEqual("passphrase"); + expect(result.category).toEqual(Type.password); }); it("passes through date parameters", () => { - const result = new GeneratedCredential("example", "password", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); expect(result.generationDate).toEqual(new Date(100)); }); it("converts numeric dates to Dates", () => { - const result = new GeneratedCredential("example", "password", 100); + const result = new GeneratedCredential("example", Type.password, 100); expect(result.generationDate).toEqual(new Date(100)); }); }); it("toJSON converts from a credential into a JSON object", () => { - const credential = new GeneratedCredential("example", "password", new Date(100)); + const credential = new GeneratedCredential("example", Type.password, new Date(100)); const result = credential.toJSON(); expect(result).toEqual({ credential: "example", - category: "password" as GeneratorCategory, + category: Type.password, generationDate: 100, }); }); @@ -42,7 +44,7 @@ describe("GeneratedCredential", () => { it("fromJSON converts Json objects into credentials", () => { const jsonValue = { credential: "example", - category: "password" as GeneratorCategory, + category: Type.password, generationDate: 100, }; @@ -51,7 +53,7 @@ describe("GeneratedCredential", () => { expect(result).toBeInstanceOf(GeneratedCredential); expect(result).toEqual({ credential: "example", - category: "password", + category: Type.password, generationDate: new Date(100), }); }); diff --git a/libs/tools/generator/extensions/history/src/generated-credential.ts b/libs/tools/generator/extensions/history/src/generated-credential.ts index 32efb752258..bec9e5ac7ee 100644 --- a/libs/tools/generator/extensions/history/src/generated-credential.ts +++ b/libs/tools/generator/extensions/history/src/generated-credential.ts @@ -1,6 +1,6 @@ import { Jsonify } from "type-fest"; -import { CredentialAlgorithm } from "@bitwarden/generator-core"; +import { CredentialType } from "@bitwarden/generator-core"; /** A credential generation result */ export class GeneratedCredential { @@ -14,7 +14,7 @@ export class GeneratedCredential { */ constructor( readonly credential: string, - readonly category: CredentialAlgorithm, + readonly category: CredentialType, generationDate: Date | number, ) { if (typeof generationDate === "number") { diff --git a/libs/tools/generator/extensions/history/src/generator-history.abstraction.ts b/libs/tools/generator/extensions/history/src/generator-history.abstraction.ts index 3b8a0e05a9e..3e3d4002be4 100644 --- a/libs/tools/generator/extensions/history/src/generator-history.abstraction.ts +++ b/libs/tools/generator/extensions/history/src/generator-history.abstraction.ts @@ -3,7 +3,7 @@ import { Observable } from "rxjs"; import { UserId } from "@bitwarden/common/types/guid"; -import { CredentialAlgorithm } from "@bitwarden/generator-core"; +import { CredentialType } from "@bitwarden/generator-core"; import { GeneratedCredential } from "./generated-credential"; @@ -29,7 +29,7 @@ export abstract class GeneratorHistoryService { track: ( userId: UserId, credential: string, - category: CredentialAlgorithm, + category: CredentialType, date?: Date, ) => Promise; diff --git a/libs/tools/generator/extensions/history/src/local-generator-history.service.spec.ts b/libs/tools/generator/extensions/history/src/local-generator-history.service.spec.ts index 3621b2c24a9..caff4234386 100644 --- a/libs/tools/generator/extensions/history/src/local-generator-history.service.spec.ts +++ b/libs/tools/generator/extensions/history/src/local-generator-history.service.spec.ts @@ -7,6 +7,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; +import { Type } from "@bitwarden/generator-core"; import { KeyService } from "@bitwarden/key-management"; import { FakeStateProvider, awaitAsync, mockAccountServiceWith } from "../../../../../common/spec"; @@ -25,7 +26,8 @@ describe("LocalGeneratorHistoryService", () => { encryptService.encryptString.mockImplementation((p) => Promise.resolve(p as unknown as EncString), ); - encryptService.decryptString.mockImplementation((c) => Promise.resolve(c.encryptedString)); + // in the test environment `c.encryptedString` always has a value + encryptService.decryptString.mockImplementation((c) => Promise.resolve(c.encryptedString!)); keyService.getUserKey.mockImplementation(() => Promise.resolve(userKey)); keyService.userKey$.mockImplementation(() => of(true as unknown as UserKey)); }); @@ -50,35 +52,35 @@ describe("LocalGeneratorHistoryService", () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password"); + await history.track(SomeUser, "example", Type.password); await awaitAsync(); const [result] = await firstValueFrom(history.credentials$(SomeUser)); - expect(result).toMatchObject({ credential: "example", category: "password" }); + expect(result).toMatchObject({ credential: "example", category: Type.password }); }); it("stores a passphrase", async () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "passphrase"); + await history.track(SomeUser, "example", Type.password); await awaitAsync(); const [result] = await firstValueFrom(history.credentials$(SomeUser)); - expect(result).toMatchObject({ credential: "example", category: "passphrase" }); + expect(result).toMatchObject({ credential: "example", category: Type.password }); }); it("stores a specific date when one is provided", async () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password", new Date(100)); + await history.track(SomeUser, "example", Type.password, new Date(100)); await awaitAsync(); const [result] = await firstValueFrom(history.credentials$(SomeUser)); expect(result).toEqual({ credential: "example", - category: "password", + category: Type.password, generationDate: new Date(100), }); }); @@ -87,13 +89,13 @@ describe("LocalGeneratorHistoryService", () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password"); - await history.track(SomeUser, "example", "password"); - await history.track(SomeUser, "example", "passphrase"); + await history.track(SomeUser, "example", Type.password); + await history.track(SomeUser, "example", Type.password); + await history.track(SomeUser, "example", Type.password); await awaitAsync(); const [firstResult, secondResult] = await firstValueFrom(history.credentials$(SomeUser)); - expect(firstResult).toMatchObject({ credential: "example", category: "password" }); + expect(firstResult).toMatchObject({ credential: "example", category: Type.password }); expect(secondResult).toBeUndefined(); }); @@ -101,13 +103,13 @@ describe("LocalGeneratorHistoryService", () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "secondResult", "password"); - await history.track(SomeUser, "firstResult", "password"); + await history.track(SomeUser, "secondResult", Type.password); + await history.track(SomeUser, "firstResult", Type.password); await awaitAsync(); const [firstResult, secondResult] = await firstValueFrom(history.credentials$(SomeUser)); - expect(firstResult).toMatchObject({ credential: "firstResult", category: "password" }); - expect(secondResult).toMatchObject({ credential: "secondResult", category: "password" }); + expect(firstResult).toMatchObject({ credential: "firstResult", category: Type.password }); + expect(secondResult).toMatchObject({ credential: "secondResult", category: Type.password }); }); it("removes history items exceeding maxTotal configuration", async () => { @@ -116,12 +118,12 @@ describe("LocalGeneratorHistoryService", () => { maxTotal: 1, }); - await history.track(SomeUser, "removed result", "password"); - await history.track(SomeUser, "example", "password"); + await history.track(SomeUser, "removed result", Type.password); + await history.track(SomeUser, "example", Type.password); await awaitAsync(); const [firstResult, secondResult] = await firstValueFrom(history.credentials$(SomeUser)); - expect(firstResult).toMatchObject({ credential: "example", category: "password" }); + expect(firstResult).toMatchObject({ credential: "example", category: Type.password }); expect(secondResult).toBeUndefined(); }); @@ -131,8 +133,8 @@ describe("LocalGeneratorHistoryService", () => { maxTotal: 1, }); - await history.track(SomeUser, "some user example", "password"); - await history.track(AnotherUser, "another user example", "password"); + await history.track(SomeUser, "some user example", Type.password); + await history.track(AnotherUser, "another user example", Type.password); await awaitAsync(); const [someFirstResult, someSecondResult] = await firstValueFrom( history.credentials$(SomeUser), @@ -143,12 +145,12 @@ describe("LocalGeneratorHistoryService", () => { expect(someFirstResult).toMatchObject({ credential: "some user example", - category: "password", + category: Type.password, }); expect(someSecondResult).toBeUndefined(); expect(anotherFirstResult).toMatchObject({ credential: "another user example", - category: "password", + category: Type.password, }); expect(anotherSecondResult).toBeUndefined(); }); @@ -167,7 +169,7 @@ describe("LocalGeneratorHistoryService", () => { it("returns null when the credential wasn't found", async () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password"); + await history.track(SomeUser, "example", Type.password); const result = await history.take(SomeUser, "not found"); @@ -177,20 +179,20 @@ describe("LocalGeneratorHistoryService", () => { it("returns a matching credential", async () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password"); + await history.track(SomeUser, "example", Type.password); const result = await history.take(SomeUser, "example"); expect(result).toMatchObject({ credential: "example", - category: "password", + category: Type.password, }); }); it("removes a matching credential", async () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password"); + await history.track(SomeUser, "example", Type.password); await history.take(SomeUser, "example"); await awaitAsync(); diff --git a/libs/tools/generator/extensions/history/src/local-generator-history.service.ts b/libs/tools/generator/extensions/history/src/local-generator-history.service.ts index cbfa55a184f..673319c1bfa 100644 --- a/libs/tools/generator/extensions/history/src/local-generator-history.service.ts +++ b/libs/tools/generator/extensions/history/src/local-generator-history.service.ts @@ -9,7 +9,7 @@ import { BufferedState } from "@bitwarden/common/tools/state/buffered-state"; import { PaddedDataPacker } from "@bitwarden/common/tools/state/padded-data-packer"; import { SecretState } from "@bitwarden/common/tools/state/secret-state"; import { UserId } from "@bitwarden/common/types/guid"; -import { CredentialAlgorithm } from "@bitwarden/generator-core"; +import { CredentialType } from "@bitwarden/generator-core"; import { KeyService } from "@bitwarden/key-management"; import { GeneratedCredential } from "./generated-credential"; @@ -36,12 +36,7 @@ export class LocalGeneratorHistoryService extends GeneratorHistoryService { private _credentialStates = new Map>(); /** {@link GeneratorHistoryService.track} */ - track = async ( - userId: UserId, - credential: string, - category: CredentialAlgorithm, - date?: Date, - ) => { + track = async (userId: UserId, credential: string, category: CredentialType, date?: Date) => { const state = this.getCredentialState(userId); let result: GeneratedCredential = null; diff --git a/libs/tools/generator/core/src/data/forwarders.ts b/libs/tools/generator/extensions/legacy/src/forwarders.ts similarity index 73% rename from libs/tools/generator/core/src/data/forwarders.ts rename to libs/tools/generator/extensions/legacy/src/forwarders.ts index e833fbf41d3..cb926ac3f66 100644 --- a/libs/tools/generator/core/src/data/forwarders.ts +++ b/libs/tools/generator/extensions/legacy/src/forwarders.ts @@ -1,4 +1,18 @@ -import { ForwarderMetadata } from "../types"; +import { IntegrationId } from "@bitwarden/common/tools/integration"; + +export type ForwarderId = IntegrationId; + +/** Metadata format for email forwarding services. */ +export type ForwarderMetadata = { + /** The unique identifier for the forwarder. */ + id: ForwarderId; + + /** The name of the service the forwarder queries. */ + name: string; + + /** Whether the forwarder is valid for self-hosted instances of Bitwarden. */ + validForSelfHosted: boolean; +}; /** Metadata about an email forwarding service. * @remarks This is used to populate the forwarder selection list diff --git a/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts b/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts index d932d013199..f575cd3b619 100644 --- a/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts +++ b/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts @@ -1,13 +1,12 @@ import { mock } from "jest-mock-extended"; import { of } from "rxjs"; -import { IntegrationId } from "@bitwarden/common/tools/integration"; +import { VendorId } from "@bitwarden/common/tools/extension"; import { UserId } from "@bitwarden/common/types/guid"; import { GeneratorService, DefaultPassphraseGenerationOptions, DefaultPasswordGenerationOptions, - Policies, PassphraseGenerationOptions, PassphraseGeneratorPolicy, PasswordGenerationOptions, @@ -38,12 +37,17 @@ const PasswordGeneratorOptionsEvaluator = policies.PasswordGeneratorOptionsEvalu function createPassphraseGenerator( options: PassphraseGenerationOptions = {}, - policy: PassphraseGeneratorPolicy = Policies.Passphrase.disabledValue, + policy?: PassphraseGeneratorPolicy, ) { let savedOptions = options; const generator = mock>({ evaluator$(id: UserId) { - const evaluator = new PassphraseGeneratorOptionsEvaluator(policy); + const active = policy ?? { + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }; + const evaluator = new PassphraseGeneratorOptionsEvaluator(active); return of(evaluator); }, options$(id: UserId) { @@ -63,12 +67,21 @@ function createPassphraseGenerator( function createPasswordGenerator( options: PasswordGenerationOptions = {}, - policy: PasswordGeneratorPolicy = Policies.Password.disabledValue, + policy?: PasswordGeneratorPolicy, ) { let savedOptions = options; const generator = mock>({ evaluator$(id: UserId) { - const evaluator = new PasswordGeneratorOptionsEvaluator(policy); + const active = policy ?? { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }; + const evaluator = new PasswordGeneratorOptionsEvaluator(active); return of(evaluator); }, options$(id: UserId) { @@ -118,7 +131,13 @@ describe("LegacyPasswordGenerationService", () => { describe("generatePassword", () => { it("invokes the inner password generator to generate passwords", async () => { const innerPassword = createPasswordGenerator(); - const generator = new LegacyPasswordGenerationService(null, null, innerPassword, null, null); + const generator = new LegacyPasswordGenerationService( + null!, + null!, + innerPassword, + null!, + null!, + ); const options = { type: "password" } as PasswordGeneratorOptions; await generator.generatePassword(options); @@ -129,11 +148,11 @@ describe("LegacyPasswordGenerationService", () => { it("invokes the inner passphrase generator to generate passphrases", async () => { const innerPassphrase = createPassphraseGenerator(); const generator = new LegacyPasswordGenerationService( - null, - null, - null, + null!, + null!, + null!, innerPassphrase, - null, + null!, ); const options = { type: "passphrase" } as PasswordGeneratorOptions; @@ -147,11 +166,11 @@ describe("LegacyPasswordGenerationService", () => { it("invokes the inner passphrase generator", async () => { const innerPassphrase = createPassphraseGenerator(); const generator = new LegacyPasswordGenerationService( - null, - null, - null, + null!, + null!, + null!, innerPassphrase, - null, + null!, ); const options = {} as PasswordGeneratorOptions; @@ -185,7 +204,7 @@ describe("LegacyPasswordGenerationService", () => { const navigation = createNavigationGenerator({ type: "passphrase", username: "word", - forwarder: "simplelogin" as IntegrationId, + forwarder: "simplelogin" as VendorId, }); const accountService = mockAccountServiceWith(SomeUser); const generator = new LegacyPasswordGenerationService( @@ -193,7 +212,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const [result] = await generator.getOptions(); @@ -220,16 +239,16 @@ describe("LegacyPasswordGenerationService", () => { }); it("sets default options when an inner service lacks a value", async () => { - const innerPassword = createPasswordGenerator(null); - const innerPassphrase = createPassphraseGenerator(null); - const navigation = createNavigationGenerator(null); + const innerPassword = createPasswordGenerator(null!); + const innerPassphrase = createPassphraseGenerator(null!); + const navigation = createNavigationGenerator(null!); const accountService = mockAccountServiceWith(SomeUser); const generator = new LegacyPasswordGenerationService( accountService, navigation, innerPassword, innerPassphrase, - null, + null!, ); const [result] = await generator.getOptions(); @@ -277,7 +296,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const [, policy] = await generator.getOptions(); @@ -323,7 +342,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const [result] = await generator.enforcePasswordGeneratorPoliciesOnOptions(options); @@ -363,7 +382,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const [result] = await generator.enforcePasswordGeneratorPoliciesOnOptions(options); @@ -409,7 +428,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const [, policy] = await generator.enforcePasswordGeneratorPoliciesOnOptions({}); @@ -441,7 +460,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const options = { type: "password" as const, @@ -474,7 +493,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const options = { type: "passphrase" as const, @@ -496,7 +515,7 @@ describe("LegacyPasswordGenerationService", () => { const navigation = createNavigationGenerator({ type: "password", username: "forwarded", - forwarder: "firefoxrelay" as IntegrationId, + forwarder: "firefoxrelay" as VendorId, }); const accountService = mockAccountServiceWith(SomeUser); const generator = new LegacyPasswordGenerationService( @@ -504,7 +523,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const options = { type: "passphrase" as const, @@ -533,9 +552,9 @@ describe("LegacyPasswordGenerationService", () => { const accountService = mockAccountServiceWith(SomeUser); const generator = new LegacyPasswordGenerationService( accountService, - null, - null, - null, + null!, + null!, + null!, history, ); @@ -552,9 +571,9 @@ describe("LegacyPasswordGenerationService", () => { const accountService = mockAccountServiceWith(SomeUser); const generator = new LegacyPasswordGenerationService( accountService, - null, - null, - null, + null!, + null!, + null!, history, ); diff --git a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts index 08ffce2eba5..5a4dce4f4a5 100644 --- a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts +++ b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts @@ -1,6 +1,15 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +import { AddyIo } from "@bitwarden/common/tools/extension/vendor/addyio"; +import { DuckDuckGo } from "@bitwarden/common/tools/extension/vendor/duckduckgo"; +import { Fastmail } from "@bitwarden/common/tools/extension/vendor/fastmail"; +import { ForwardEmail } from "@bitwarden/common/tools/extension/vendor/forwardemail"; +import { Mozilla } from "@bitwarden/common/tools/extension/vendor/mozilla"; +import { SimpleLogin } from "@bitwarden/common/tools/extension/vendor/simplelogin"; +import { IntegrationId } from "@bitwarden/common/tools/integration"; import { UserId } from "@bitwarden/common/types/guid"; import { ApiOptions, @@ -13,13 +22,6 @@ import { DefaultCatchallOptions, DefaultEffUsernameOptions, EffUsernameGenerationOptions, - DefaultAddyIoOptions, - DefaultDuckDuckGoOptions, - DefaultFastmailOptions, - DefaultFirefoxRelayOptions, - DefaultForwardEmailOptions, - DefaultSimpleLoginOptions, - Forwarders, DefaultSubaddressOptions, SubaddressGenerationOptions, policies, @@ -169,7 +171,7 @@ describe("LegacyUsernameGenerationService", () => { // set up an arbitrary forwarder for the username test; all forwarders tested in their own tests const options = { type: "forwarded", - forwardedService: Forwarders.AddyIo.id, + forwardedService: AddyIo.id, } as UsernameGeneratorOptions; const addyIo = createGenerator(null, null); addyIo.generate.mockResolvedValue("addyio@example.com"); @@ -249,7 +251,7 @@ describe("LegacyUsernameGenerationService", () => { describe("generateForwarded", () => { it("should generate a AddyIo username", async () => { const options = { - forwardedService: Forwarders.AddyIo.id, + forwardedService: AddyIo.id, forwardedAnonAddyApiToken: "token", forwardedAnonAddyBaseUrl: "https://example.com", forwardedAnonAddyDomain: "example.com", @@ -284,7 +286,7 @@ describe("LegacyUsernameGenerationService", () => { it("should generate a DuckDuckGo username", async () => { const options = { - forwardedService: Forwarders.DuckDuckGo.id, + forwardedService: DuckDuckGo.id, forwardedDuckDuckGoToken: "token", website: "example.com", } as UsernameGeneratorOptions; @@ -315,7 +317,7 @@ describe("LegacyUsernameGenerationService", () => { it("should generate a Fastmail username", async () => { const options = { - forwardedService: Forwarders.Fastmail.id, + forwardedService: Fastmail.id, forwardedFastmailApiToken: "token", website: "example.com", } as UsernameGeneratorOptions; @@ -346,7 +348,7 @@ describe("LegacyUsernameGenerationService", () => { it("should generate a FirefoxRelay username", async () => { const options = { - forwardedService: Forwarders.FirefoxRelay.id, + forwardedService: Mozilla.id, forwardedFirefoxApiToken: "token", website: "example.com", } as UsernameGeneratorOptions; @@ -377,7 +379,7 @@ describe("LegacyUsernameGenerationService", () => { it("should generate a ForwardEmail username", async () => { const options = { - forwardedService: Forwarders.ForwardEmail.id, + forwardedService: ForwardEmail.id, forwardedForwardEmailApiToken: "token", forwardedForwardEmailDomain: "example.com", website: "example.com", @@ -410,7 +412,7 @@ describe("LegacyUsernameGenerationService", () => { it("should generate a SimpleLogin username", async () => { const options = { - forwardedService: Forwarders.SimpleLogin.id, + forwardedService: SimpleLogin.id, forwardedSimpleLoginApiKey: "token", forwardedSimpleLoginBaseUrl: "https://example.com", website: "example.com", @@ -449,7 +451,7 @@ describe("LegacyUsernameGenerationService", () => { const navigation = createNavigationGenerator({ type: "username", username: "catchall", - forwarder: Forwarders.AddyIo.id, + forwarder: AddyIo.id, }); const catchall = createGenerator( @@ -557,7 +559,7 @@ describe("LegacyUsernameGenerationService", () => { subaddressEmail: "foo@example.com", catchallType: "random", catchallDomain: "example.com", - forwardedService: Forwarders.AddyIo.id, + forwardedService: AddyIo.id, forwardedAnonAddyApiToken: "addyIoToken", forwardedAnonAddyDomain: "addyio.example.com", forwardedAnonAddyBaseUrl: "https://addyio.api.example.com", @@ -583,21 +585,36 @@ describe("LegacyUsernameGenerationService", () => { null, DefaultSubaddressOptions, ); - const addyIo = createGenerator( - null, - DefaultAddyIoOptions, - ); - const duckDuckGo = createGenerator(null, DefaultDuckDuckGoOptions); - const fastmail = createGenerator( - null, - DefaultFastmailOptions, - ); - const firefoxRelay = createGenerator(null, DefaultFirefoxRelayOptions); - const forwardEmail = createGenerator( - null, - DefaultForwardEmailOptions, - ); - const simpleLogin = createGenerator(null, DefaultSimpleLoginOptions); + const addyIo = createGenerator(null, { + website: null, + baseUrl: "https://app.addy.io", + token: "", + domain: "", + }); + const duckDuckGo = createGenerator(null, { + website: null, + token: "", + }); + const fastmail = createGenerator(null, { + website: "", + domain: "", + prefix: "", + token: "", + }); + const firefoxRelay = createGenerator(null, { + website: null, + token: "", + }); + const forwardEmail = createGenerator(null, { + website: null, + token: "", + domain: "", + }); + const simpleLogin = createGenerator(null, { + website: null, + baseUrl: "https://app.simplelogin.io", + token: "", + }); const generator = new LegacyUsernameGenerationService( account, @@ -624,16 +641,16 @@ describe("LegacyUsernameGenerationService", () => { subaddressType: DefaultSubaddressOptions.subaddressType, subaddressEmail: DefaultSubaddressOptions.subaddressEmail, forwardedService: DefaultGeneratorNavigation.forwarder, - forwardedAnonAddyApiToken: DefaultAddyIoOptions.token, - forwardedAnonAddyDomain: DefaultAddyIoOptions.domain, - forwardedAnonAddyBaseUrl: DefaultAddyIoOptions.baseUrl, - forwardedDuckDuckGoToken: DefaultDuckDuckGoOptions.token, - forwardedFastmailApiToken: DefaultFastmailOptions.token, - forwardedFirefoxApiToken: DefaultFirefoxRelayOptions.token, - forwardedForwardEmailApiToken: DefaultForwardEmailOptions.token, - forwardedForwardEmailDomain: DefaultForwardEmailOptions.domain, - forwardedSimpleLoginApiKey: DefaultSimpleLoginOptions.token, - forwardedSimpleLoginBaseUrl: DefaultSimpleLoginOptions.baseUrl, + forwardedAnonAddyApiToken: "", + forwardedAnonAddyDomain: "", + forwardedAnonAddyBaseUrl: "https://app.addy.io", + forwardedDuckDuckGoToken: "", + forwardedFastmailApiToken: "", + forwardedFirefoxApiToken: "", + forwardedForwardEmailApiToken: "", + forwardedForwardEmailDomain: "", + forwardedSimpleLoginApiKey: "", + forwardedSimpleLoginBaseUrl: "https://app.simplelogin.io", }); }); }); @@ -678,7 +695,7 @@ describe("LegacyUsernameGenerationService", () => { subaddressEmail: "foo@example.com", catchallType: "random", catchallDomain: "example.com", - forwardedService: Forwarders.AddyIo.id, + forwardedService: AddyIo.id as IntegrationId, forwardedAnonAddyApiToken: "addyIoToken", forwardedAnonAddyDomain: "addyio.example.com", forwardedAnonAddyBaseUrl: "https://addyio.api.example.com", @@ -697,7 +714,7 @@ describe("LegacyUsernameGenerationService", () => { expect(navigation.saveOptions).toHaveBeenCalledWith(SomeUser, { type: "password", username: "catchall", - forwarder: Forwarders.AddyIo.id, + forwarder: AddyIo.id, }); expect(catchall.saveOptions).toHaveBeenCalledWith(SomeUser, { diff --git a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts index c6e9118535f..48da3404cd6 100644 --- a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts +++ b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts @@ -3,6 +3,7 @@ import { zip, firstValueFrom, map, concatMap, combineLatest } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationRequest } from "@bitwarden/common/tools/integration/rpc"; import { UserId } from "@bitwarden/common/types/guid"; import { @@ -14,13 +15,13 @@ import { GeneratorService, CatchallGenerationOptions, EffUsernameGenerationOptions, - Forwarders, SubaddressGenerationOptions, UsernameGeneratorType, ForwarderId, } from "@bitwarden/generator-core"; import { GeneratorNavigationService, GeneratorNavigation } from "@bitwarden/generator-navigation"; +import { Forwarders } from "./forwarders"; import { UsernameGeneratorOptions } from "./username-generation-options"; import { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction"; @@ -89,12 +90,14 @@ export class LegacyUsernameGenerationService implements UsernameGenerationServic const stored = this.toStoredOptions(options); switch (options.forwardedService) { case Forwarders.AddyIo.id: + case Vendor.addyio: return this.addyIo.generate(stored.forwarders.addyIo); case Forwarders.DuckDuckGo.id: return this.duckDuckGo.generate(stored.forwarders.duckDuckGo); case Forwarders.Fastmail.id: return this.fastmail.generate(stored.forwarders.fastmail); case Forwarders.FirefoxRelay.id: + case Vendor.mozilla: return this.firefoxRelay.generate(stored.forwarders.firefoxRelay); case Forwarders.ForwardEmail.id: return this.forwardEmail.generate(stored.forwarders.forwardEmail); @@ -232,22 +235,24 @@ export class LegacyUsernameGenerationService implements UsernameGenerationServic options: MappedOptions, ) { switch (forwarder) { - case "anonaddy": + case Forwarders.AddyIo.id: + case Vendor.addyio: await this.addyIo.saveOptions(account, options.forwarders.addyIo); return true; - case "duckduckgo": + case Forwarders.DuckDuckGo.id: await this.duckDuckGo.saveOptions(account, options.forwarders.duckDuckGo); return true; - case "fastmail": + case Forwarders.Fastmail.id: await this.fastmail.saveOptions(account, options.forwarders.fastmail); return true; - case "firefoxrelay": + case Forwarders.FirefoxRelay.id: + case Vendor.mozilla: await this.firefoxRelay.saveOptions(account, options.forwarders.firefoxRelay); return true; - case "forwardemail": + case Forwarders.ForwardEmail.id: await this.forwardEmail.saveOptions(account, options.forwarders.forwardEmail); return true; - case "simplelogin": + case Forwarders.SimpleLogin.id: await this.simpleLogin.saveOptions(account, options.forwarders.simpleLogin); return true; default: diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts index 218121a3a75..82b9e29e91a 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts @@ -24,7 +24,7 @@ describe("GeneratorNavigationEvaluator", () => { describe("applyPolicy", () => { it("returns the input options when a policy is not in effect", () => { - const evaluator = new GeneratorNavigationEvaluator(null); + const evaluator = new GeneratorNavigationEvaluator(null!); const options = { type: "password" as const }; const result = evaluator.applyPolicy(options); @@ -54,7 +54,7 @@ describe("GeneratorNavigationEvaluator", () => { }); it("defaults options to the default generator navigation type when a policy is not in effect", () => { - const evaluator = new GeneratorNavigationEvaluator(null); + const evaluator = new GeneratorNavigationEvaluator(null!); const result = evaluator.sanitize({}); diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts index 2f891cdea03..5446c1f26ad 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { PasswordAlgorithms, PolicyEvaluator } from "@bitwarden/generator-core"; +import { AlgorithmsByType, PolicyEvaluator, Type } from "@bitwarden/generator-core"; import { DefaultGeneratorNavigation } from "./default-generator-navigation"; import { GeneratorNavigation } from "./generator-navigation"; @@ -19,7 +19,7 @@ export class GeneratorNavigationEvaluator /** {@link PolicyEvaluator.policyInEffect} */ get policyInEffect(): boolean { - return PasswordAlgorithms.includes(this.policy?.overridePasswordType); + return AlgorithmsByType[Type.password].includes(this.policy?.overridePasswordType); } /** Apply policy to the input options. diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts index cba1f91dad3..fc920a66e0d 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts @@ -4,14 +4,14 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; // FIXME: use index.ts imports once policy abstractions and models // implement ADR-0002 import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { PasswordType } from "@bitwarden/generator-core"; +import { PasswordAlgorithm } from "@bitwarden/generator-core"; /** Policy settings affecting password generator navigation */ export type GeneratorNavigationPolicy = { /** The type of generator that should be shown by default when opening * the password generator. */ - overridePasswordType?: PasswordType; + overridePasswordType?: PasswordAlgorithm; }; /** Reduces a policy into an accumulator by preferring the password generator diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation.ts index 5a35e57d7b4..08e5025244d 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation.ts @@ -1,4 +1,5 @@ -import { GeneratorType, ForwarderId, UsernameGeneratorType } from "@bitwarden/generator-core"; +import { VendorId } from "@bitwarden/common/tools/extension"; +import { UsernameGeneratorType, CredentialAlgorithm } from "@bitwarden/generator-core"; /** Stores credential generator UI state. */ export type GeneratorNavigation = { @@ -6,11 +7,11 @@ export type GeneratorNavigation = { * @remarks The legacy generator only supports "password" and "passphrase". * The componentized generator supports all values. */ - type?: GeneratorType; + type?: CredentialAlgorithm; /** When `type === "username"`, this stores the username algorithm. */ username?: UsernameGeneratorType; /** When `username === "forwarded"`, this stores the forwarder implementation. */ - forwarder?: ForwarderId | ""; + forwarder?: VendorId | ""; }; diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts index 8d24fc4acee..30775aa8a83 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts @@ -28,7 +28,7 @@ import { ToastService, TypographyModule, } from "@bitwarden/components"; -import { CredentialGeneratorService, GenerateRequest, Generators } from "@bitwarden/generator-core"; +import { CredentialGeneratorService, GenerateRequest, Type } from "@bitwarden/generator-core"; import { SendFormConfig } from "../../abstractions/send-form-config.service"; import { SendFormContainer } from "../../send-form-container"; @@ -122,12 +122,12 @@ export class SendOptionsComponent implements OnInit { } generatePassword = async () => { - const on$ = new BehaviorSubject({ source: "send" }); + const on$ = new BehaviorSubject({ source: "send", type: Type.password }); const account$ = this.accountService.activeAccount$.pipe( pin({ name: () => "send-options.component", distinct: (p, c) => p.id === c.id }), ); const generatedCredential = await firstValueFrom( - this.generatorService.generate$(Generators.password, { on$, account$ }), + this.generatorService.generate$({ on$, account$ }), ); this.sendOptionsForm.patchValue({ From b2d38249ba33416faa8c88ed361ae18cc330ff86 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Tue, 27 May 2025 16:14:36 +0200 Subject: [PATCH 28/41] Remove class user-select: The copy button contains all the information and is the preferred method (#14944) Co-authored-by: Daniel James Smith --- .../popup/settings/about-dialog/about-dialog.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html index 40dad4cde4b..af68959fe5d 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html @@ -6,7 +6,7 @@

© Bitwarden Inc. 2015-{{ year }}

-
+

{{ "version" | i18n }}: {{ version$ | async }}

SDK: {{ sdkVersion$ | async }}

From d1aa8422e0319ac3b33dd5d9365162ecfe3fa4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Tue, 27 May 2025 11:09:50 -0400 Subject: [PATCH 29/41] align sdk generator types (#14967) --- .../core/src/metadata/password/sdk-eff-word-list.spec.ts | 3 ++- .../core/src/metadata/password/sdk-eff-word-list.ts | 9 +++------ .../src/metadata/password/sdk-random-password.spec.ts | 3 ++- .../core/src/metadata/password/sdk-random-password.ts | 9 +++------ 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts index 03f1275f73c..29378b4cdd3 100644 --- a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts +++ b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts @@ -5,7 +5,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { SdkPasswordRandomizer } from "../../engine"; import { PassphrasePolicyConstraints } from "../../policies"; -import { PassphraseGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { PassphraseGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts index 802ef0ef068..8892016b609 100644 --- a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts +++ b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts @@ -6,17 +6,14 @@ import { BitwardenClient } from "@bitwarden/sdk-internal"; import { SdkPasswordRandomizer } from "../../engine"; import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies"; -import { - CredentialGenerator, - GeneratorDependencyProvider, - PassphraseGenerationOptions, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, PassphraseGenerationOptions } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const sdkPassphrase: GeneratorMetadata = { id: Algorithm.sdkPassphrase, - category: Type.password, + type: Type.password, weight: 130, i18nKeys: { name: "passphrase", diff --git a/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts b/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts index cec4704789f..1e9cf6dbd87 100644 --- a/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts +++ b/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts @@ -5,7 +5,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { SdkPasswordRandomizer } from "../../engine"; import { DynamicPasswordPolicyConstraints } from "../../policies"; -import { PasswordGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { PasswordGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts b/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts index d4bb6263b1e..d6544fa115e 100644 --- a/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts +++ b/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts @@ -6,17 +6,14 @@ import { BitwardenClient } from "@bitwarden/sdk-internal"; import { SdkPasswordRandomizer } from "../../engine"; import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies"; -import { - CredentialGenerator, - GeneratorDependencyProvider, - PasswordGeneratorSettings, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, PasswordGeneratorSettings } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const sdkPassword: GeneratorMetadata = deepFreeze({ id: Algorithm.sdkPassword, - category: Type.password, + type: Type.password, weight: 120, i18nKeys: { name: "password", From abb01d9038819f39e8937dec6e995b9db62f7e44 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Tue, 27 May 2025 11:14:10 -0400 Subject: [PATCH 30/41] ensure loginview properties have correct defaults when usding SDK decryption (#14948) --- libs/common/src/vault/models/view/login.view.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/vault/models/view/login.view.ts b/libs/common/src/vault/models/view/login.view.ts index 41568f643d5..6bdc23f42b1 100644 --- a/libs/common/src/vault/models/view/login.view.ts +++ b/libs/common/src/vault/models/view/login.view.ts @@ -118,7 +118,7 @@ export class LoginView extends ItemView { const passwordRevisionDate = obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); - const uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)); + const uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)) || []; return Object.assign(new LoginView(), obj, { passwordRevisionDate, From 0f9f6a1c5ddfccc2632011066375cb1b847a6a47 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 18:58:56 +0200 Subject: [PATCH 31/41] [deps] Tools: Update papaparse to v5.5.3 (#14966) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 4c8d2918ce1..eeebf4dad6f 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -86,7 +86,7 @@ "node-fetch": "2.6.12", "node-forge": "1.3.1", "open": "8.4.2", - "papaparse": "5.5.2", + "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", "tldts": "7.0.1", diff --git a/package-lock.json b/package-lock.json index ff21acbc20b..f0dd03b5e74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,7 @@ "node-forge": "1.3.1", "oidc-client-ts": "2.4.1", "open": "8.4.2", - "papaparse": "5.5.2", + "papaparse": "5.5.3", "patch-package": "8.0.0", "proper-lockfile": "4.1.2", "qrcode-parser": "2.1.3", @@ -111,7 +111,7 @@ "@types/node": "22.15.3", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", - "@types/papaparse": "5.3.15", + "@types/papaparse": "5.3.16", "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", @@ -219,7 +219,7 @@ "node-fetch": "2.6.12", "node-forge": "1.3.1", "open": "8.4.2", - "papaparse": "5.5.2", + "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", "tldts": "7.0.1", @@ -11939,9 +11939,9 @@ } }, "node_modules/@types/papaparse": { - "version": "5.3.15", - "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.15.tgz", - "integrity": "sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.16.tgz", + "integrity": "sha512-T3VuKMC2H0lgsjI9buTB3uuKj3EMD2eap1MOuEQuBQ44EnDx/IkGhU6EwiTf9zG3za4SKlmwKAImdDKdNnCsXg==", "dev": true, "license": "MIT", "dependencies": { @@ -30738,9 +30738,9 @@ "license": "(MIT AND Zlib)" }, "node_modules/papaparse": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.2.tgz", - "integrity": "sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", + "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", "license": "MIT" }, "node_modules/param-case": { diff --git a/package.json b/package.json index f63db41e1f3..fe7f69ff40d 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "@types/node": "22.15.3", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", - "@types/papaparse": "5.3.15", + "@types/papaparse": "5.3.16", "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", @@ -194,7 +194,7 @@ "node-forge": "1.3.1", "oidc-client-ts": "2.4.1", "open": "8.4.2", - "papaparse": "5.5.2", + "papaparse": "5.5.3", "patch-package": "8.0.0", "proper-lockfile": "4.1.2", "qrcode-parser": "2.1.3", From 5f169af08e69c4b957df3e5c9aa6baef56cb4616 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 27 May 2025 10:03:29 -0700 Subject: [PATCH 32/41] [PM-21122] - Hide orgs in product switcher for single org policy users (#14803) * don't display orgs in account switcher for single org policy users * add comment * add test case * fix storybook * fix storybook again * use variable name instead of comment --- .../navigation-switcher.stories.ts | 7 +++++++ .../product-switcher/product-switcher.stories.ts | 7 +++++++ .../shared/product-switcher.service.spec.ts | 16 ++++++++++++++++ .../shared/product-switcher.service.ts | 16 ++++++++++++++-- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index b1c1a0a906a..0ecec9d8944 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -5,6 +5,7 @@ import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; @@ -130,6 +131,12 @@ export default { return new I18nMockService(translations); }, }, + { + provide: PolicyService, + useValue: { + policyAppliesToUser$: () => of(false), + }, + }, ], }), applicationConfig({ diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index 4525105e579..0b7304a3657 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -5,6 +5,7 @@ import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; @@ -126,6 +127,12 @@ export default { }); }, }, + { + provide: PolicyService, + useValue: { + policyAppliesToUser$: () => of(false), + }, + }, ], }), applicationConfig({ diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts index a1ac434d590..4abd85d7991 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts @@ -7,6 +7,7 @@ import { Observable, firstValueFrom, of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; @@ -27,6 +28,7 @@ describe("ProductSwitcherService", () => { let accountService: FakeAccountService; let platformUtilsService: MockProxy; let activeRouteParams = convertToParamMap({ organizationId: "1234" }); + let singleOrgPolicyEnabled = false; const getLastSync = jest.fn().mockResolvedValue(new Date("2024-05-14")); const userId = Utils.newGuid() as UserId; @@ -77,6 +79,12 @@ describe("ProductSwitcherService", () => { provide: SyncService, useValue: { getLastSync }, }, + { + provide: PolicyService, + useValue: { + policyAppliesToUser$: () => of(singleOrgPolicyEnabled), + }, + }, ], }); }); @@ -184,6 +192,14 @@ describe("ProductSwitcherService", () => { expect(products.bento.find((p) => p.name === "Admin Console")).toBeDefined(); expect(products.other.find((p) => p.name === "Organizations")).toBeUndefined(); }); + + it("does not include Organizations when the user's single org policy is enabled", async () => { + singleOrgPolicyEnabled = true; + initiateService(); + const products = await firstValueFrom(service.products$); + + expect(products.other.find((p) => p.name === "Organizations")).not.toBeDefined(); + }); }); describe("Provider Portal", () => { diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts index ec0d2c2651c..53ec3b0840f 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts @@ -6,6 +6,7 @@ import { combineLatest, concatMap, filter, + firstValueFrom, map, Observable, ReplaySubject, @@ -18,10 +19,12 @@ import { canAccessOrgAdmin, OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderType } from "@bitwarden/common/admin-console/enums"; +import { PolicyType, ProviderType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; @@ -104,6 +107,7 @@ export class ProductSwitcherService { private syncService: SyncService, private accountService: AccountService, private platformUtilsService: PlatformUtilsService, + private policyService: PolicyService, ) { this.pollUntilSynced(); } @@ -235,7 +239,15 @@ export class ProductSwitcherService { if (acOrg) { bento.push(products.ac); } else { - other.push(products.orgs); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getUserId), + ); + const userHasSingleOrgPolicy = await firstValueFrom( + this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, activeUserId), + ); + if (!userHasSingleOrgPolicy) { + other.push(products.orgs); + } } if (providers.length > 0) { From 677a435cadddf26dbca9461d78a0950c52360c5f Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 27 May 2025 10:05:58 -0700 Subject: [PATCH 33/41] prevent showing ssh key when cipher changes in desktop view (#14913) --- apps/desktop/src/vault/app/vault/view.component.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 68bf8d6d1a3..33dfc600265 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -9,6 +9,7 @@ import { OnDestroy, OnInit, Output, + SimpleChanges, } from "@angular/core"; import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/components/view.component"; @@ -130,7 +131,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } - async ngOnChanges() { + async ngOnChanges(changes: SimpleChanges) { if (this.cipher?.decryptionFailure) { DecryptionFailureDialogComponent.open(this.dialogService, { cipherIds: [this.cipherId as CipherId], @@ -138,6 +139,12 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro return; } this.passwordReprompted = this.masterPasswordAlreadyPrompted; + + if (changes["cipherId"]) { + if (changes["cipherId"].currentValue !== changes["cipherId"].previousValue) { + this.showPrivateKey = false; + } + } } viewHistory() { From 5423ab326898c77950172b4f8cdd852a7f12d1a5 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 27 May 2025 19:13:15 +0200 Subject: [PATCH 34/41] [PM-21934] Upgrade to eslint 9 (#14754) Upgrades to Eslint v9. Since this is a major version there were breaking changes, but since we've previously migrated to flat configs in #12806 those were minimal. --- .github/renovate.json5 | 2 + .../vault-timeout/vault-timeout.service.ts | 1 - .../popup/services/browser-router.service.ts | 2 + .../view-cache/popup-router-cache.service.ts | 2 + apps/cli/src/utils.ts | 1 - .../organization-reporting-routing.module.ts | 6 +- .../change-plan-dialog.component.ts | 2 + .../organization-payment-method.component.ts | 2 + .../shared/payment-method.component.ts | 2 + apps/web/src/app/core/router.service.ts | 3 + eslint.config.mjs | 6 +- .../device-trust.service.implementation.ts | 4 + libs/eslint/fix-jsdom.ts | 10 + libs/eslint/jest.config.js | 1 + libs/eslint/test.setup.mjs | 2 - libs/eslint/tsconfig.json | 3 +- .../dashlane/dashlane-csv-importer.ts | 2 +- package-lock.json | 2758 ++++++++++++----- package.json | 9 +- 19 files changed, 1961 insertions(+), 857 deletions(-) create mode 100644 libs/eslint/fix-jsdom.ts diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1b84ccdab01..f30bc06e4a2 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -59,6 +59,7 @@ { matchPackageNames: [ "@angular-eslint/schematics", + "@eslint/compat", "@typescript-eslint/rule-tester", "@typescript-eslint/utils", "angular-eslint", @@ -81,6 +82,7 @@ { matchPackageNames: [ "@angular-eslint/schematics", + "@eslint/compat", "@typescript-eslint/rule-tester", "@typescript-eslint/utils", "angular-eslint", diff --git a/apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts b/apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts index 51f90fb98a6..c0de04b02d7 100644 --- a/apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts +++ b/apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts @@ -17,7 +17,6 @@ export default class VaultTimeoutService extends BaseVaultTimeoutService { // setIntervals. It works by calling the native extension which sleeps for 10s, // efficiently replicating setInterval. async checkSafari() { - // eslint-disable-next-line while (true) { try { await SafariApp.sendMessageToApp("sleep"); diff --git a/apps/browser/src/platform/popup/services/browser-router.service.ts b/apps/browser/src/platform/popup/services/browser-router.service.ts index 413bde5fcad..2d449b8a0f2 100644 --- a/apps/browser/src/platform/popup/services/browser-router.service.ts +++ b/apps/browser/src/platform/popup/services/browser-router.service.ts @@ -21,6 +21,8 @@ export class BrowserRouterService { child = child.firstChild; } + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression const updateUrl = !child?.data?.doNotSaveUrl ?? true; if (updateUrl) { diff --git a/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts index aa0c0854eff..5fc508ac2a6 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts @@ -62,6 +62,8 @@ export class PopupRouterCacheService { child = child.firstChild; } + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression return !child?.data?.doNotSaveUrl ?? true; }), switchMap((event) => this.push(event.url)), diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts index fadede9c71b..e321adbfd5e 100644 --- a/apps/cli/src/utils.ts +++ b/apps/cli/src/utils.ts @@ -161,7 +161,6 @@ export class CliUtils { process.stdin.setEncoding("utf8"); process.stdin.on("readable", () => { - // eslint-disable-next-line while (true) { const chunk = process.stdin.read(); if (chunk == null) { diff --git a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts index 23751653331..4c825b26bb2 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts @@ -9,12 +9,16 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +// eslint-disable-next-line no-restricted-imports import { ExposedPasswordsReportComponent } from "../../../dirt/reports/pages/organizations/exposed-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports import { InactiveTwoFactorReportComponent } from "../../../dirt/reports/pages/organizations/inactive-two-factor-report.component"; +// eslint-disable-next-line no-restricted-imports import { ReusedPasswordsReportComponent } from "../../../dirt/reports/pages/organizations/reused-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports import { UnsecuredWebsitesReportComponent } from "../../../dirt/reports/pages/organizations/unsecured-websites-report.component"; +// eslint-disable-next-line no-restricted-imports import { WeakPasswordsReportComponent } from "../../../dirt/reports/pages/organizations/weak-passwords-report.component"; -/* eslint no-restricted-imports: "error" */ import { isPaidOrgGuard } from "../guards/is-paid-org.guard"; import { organizationPermissionsGuard } from "../guards/org-permissions.guard"; import { organizationRedirectGuard } from "../guards/org-redirect.guard"; diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index 9b6694a3bbe..a6e8670d944 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -607,6 +607,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return ( plan.PasswordManager.additionalStoragePricePerGb * + // TODO: Eslint upgrade. Please resolve this since the null check does nothing + // eslint-disable-next-line no-constant-binary-expression Math.abs(this.sub?.maxStorageGb ? this.sub?.maxStorageGb - 1 : 0 || 0) ); } diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts index c896ee6404c..fbd7453c712 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts @@ -148,6 +148,8 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { paymentSource, ); } + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression this.isUnpaid = this.subscriptionStatus === "unpaid" ?? false; // If the flag `launchPaymentModalAutomatically` is set to true, // we schedule a timeout (delay of 800ms) to automatically launch the payment modal. diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index 27c9caf7186..74793bccc01 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -143,6 +143,8 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { [this.billing, this.sub] = await Promise.all([billingPromise, subPromise]); } + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression this.isUnpaid = this.subscription?.status === "unpaid" ?? false; this.loading = false; // If the flag `launchPaymentModalAutomatically` is set to true, diff --git a/apps/web/src/app/core/router.service.ts b/apps/web/src/app/core/router.service.ts index ff0aea47b9a..603c171e95b 100644 --- a/apps/web/src/app/core/router.service.ts +++ b/apps/web/src/app/core/router.service.ts @@ -74,6 +74,9 @@ export class RouterService { const titleId: string = child?.snapshot?.data?.titleId; const rawTitle: string = child?.snapshot?.data?.title; + + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression const updateUrl = !child?.snapshot?.data?.doNotSaveUrl ?? true; if (titleId != null || rawTitle != null) { diff --git a/eslint.config.mjs b/eslint.config.mjs index aa0b475da0b..5a4874457a0 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,5 +1,5 @@ // @ts-check - +import { fixupPluginRules } from "@eslint/compat"; import eslint from "@eslint/js"; import tseslint from "typescript-eslint"; import angular from "angular-eslint"; @@ -31,8 +31,8 @@ export default tseslint.config( reportUnusedDisableDirectives: "error", }, plugins: { - rxjs: rxjs, - "rxjs-angular": angularRxjs, + rxjs: fixupPluginRules(rxjs), + "rxjs-angular": fixupPluginRules(angularRxjs), "@bitwarden/platform": platformPlugins, }, languageOptions: { diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index 84ebf981f03..ecfeb10dcda 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -89,6 +89,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ) { this.supportsDeviceTrust$ = this.userDecryptionOptionsService.userDecryptionOptions$.pipe( map((options) => { + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression return options?.trustedDeviceOption != null ?? false; }), ); @@ -97,6 +99,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { supportsDeviceTrustByUserId$(userId: UserId): Observable { return this.userDecryptionOptionsService.userDecryptionOptionsById$(userId).pipe( map((options) => { + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression return options?.trustedDeviceOption != null ?? false; }), ); diff --git a/libs/eslint/fix-jsdom.ts b/libs/eslint/fix-jsdom.ts new file mode 100644 index 00000000000..f7c29a62723 --- /dev/null +++ b/libs/eslint/fix-jsdom.ts @@ -0,0 +1,10 @@ +import JSDOMEnvironment from "jest-environment-jsdom"; + +export default class FixJSDOMEnvironment extends JSDOMEnvironment { + constructor(...args: ConstructorParameters) { + super(...args); + + // FIXME https://github.com/jsdom/jsdom/issues/3363 + this.global.structuredClone = structuredClone; + } +} diff --git a/libs/eslint/jest.config.js b/libs/eslint/jest.config.js index 67436ff906e..5acadb023e4 100644 --- a/libs/eslint/jest.config.js +++ b/libs/eslint/jest.config.js @@ -3,6 +3,7 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); /** @type {import('jest').Config} */ module.exports = { ...sharedConfig, + testEnvironment: "./fix-jsdom.ts", testMatch: ["**/+(*.)+(spec).+(mjs)"], displayName: "libs/eslint tests", preset: "jest-preset-angular", diff --git a/libs/eslint/test.setup.mjs b/libs/eslint/test.setup.mjs index 45de6fcf3e1..19f35df9e7f 100644 --- a/libs/eslint/test.setup.mjs +++ b/libs/eslint/test.setup.mjs @@ -1,5 +1,3 @@ -/* eslint-disable no-undef */ - import { clearImmediate, setImmediate } from "node:timers"; Object.defineProperties(globalThis, { diff --git a/libs/eslint/tsconfig.json b/libs/eslint/tsconfig.json index eb2bb889d1a..bbf065886c4 100644 --- a/libs/eslint/tsconfig.json +++ b/libs/eslint/tsconfig.json @@ -1,5 +1,6 @@ { "extends": "../shared/tsconfig", "compilerOptions": {}, - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist"], + "files": ["empty.ts"] } diff --git a/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts b/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts index 5b282fb466c..2ead8c4bb21 100644 --- a/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts +++ b/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts @@ -92,7 +92,7 @@ export class DashlaneCsvImporter extends BaseImporter implements Importer { this.parseIdRecord(cipher, row); } - if ((rowKeys[0] === "type") != null && rowKeys[1] === "title") { + if (rowKeys[0] === "type" && rowKeys[1] === "title") { this.parsePersonalInformationRecord(cipher, row); } diff --git a/package-lock.json b/package-lock.json index f0dd03b5e74..691705cc280 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "@compodoc/compodoc": "1.1.26", "@electron/notarize": "2.5.0", "@electron/rebuild": "3.7.2", + "@eslint/compat": "1.2.9", "@lit-labs/signals": "0.1.2", "@ngtools/webpack": "18.2.19", "@storybook/addon-a11y": "8.6.12", @@ -136,7 +137,7 @@ "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.6.4", - "eslint": "8.57.1", + "eslint": "9.26.0", "eslint-config-prettier": "10.1.2", "eslint-import-resolver-typescript": "4.3.4", "eslint-plugin-import": "2.31.0", @@ -398,14 +399,14 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1902.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.12.tgz", - "integrity": "sha512-LfUc7k84YL290hAxsG+FvjQpXugQXyw5aDzrQQB4iTYhBgaABu2aaNOU4eu3JH+F8NeXd2EBF/YMr2LDSkYlMw==", + "version": "0.1902.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.13.tgz", + "integrity": "sha512-ZMj+PjK22Ph2U8usG6L7LqEfvWlbaOvmiWXSrEt9YiC9QJt6rsumCkOgUIsmHQtucm/lK+9CMtyYdwH2fYycjg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@angular-devkit/core": "19.2.12", + "@angular-devkit/core": "19.2.13", "rxjs": "7.8.1" }, "engines": { @@ -762,6 +763,20 @@ "@types/send": "*" } }, + "node_modules/@angular-devkit/build-angular/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -828,6 +843,48 @@ "webpack": ">=5" } }, + "node_modules/@angular-devkit/build-angular/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/@angular-devkit/build-angular/node_modules/browserslist": { "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", @@ -899,6 +956,19 @@ "node": ">= 6" } }, + "node_modules/@angular-devkit/build-angular/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -906,6 +976,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@angular-devkit/build-angular/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@angular-devkit/build-angular/node_modules/copy-webpack-plugin": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", @@ -968,6 +1055,116 @@ "node": ">=4.0" } }, + "node_modules/@angular-devkit/build-angular/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-devkit/build-angular/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-devkit/build-angular/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -1003,6 +1200,19 @@ "node": ">= 14" } }, + "node_modules/@angular-devkit/build-angular/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", @@ -1010,6 +1220,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@angular-devkit/build-angular/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -1076,6 +1296,62 @@ "dev": true, "license": "ISC" }, + "node_modules/@angular-devkit/build-angular/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/mini-css-extract-plugin": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", @@ -1097,6 +1373,16 @@ "webpack": "^5.0.0" } }, + "node_modules/@angular-devkit/build-angular/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/open": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", @@ -1133,6 +1419,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@angular-devkit/build-angular/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@angular-devkit/build-angular/node_modules/postcss": { "version": "8.4.41", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", @@ -1162,6 +1455,38 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/@angular-devkit/build-angular/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1263,6 +1588,88 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-devkit/build-angular/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/webpack": { "version": "5.94.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", @@ -1578,9 +1985,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "19.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz", - "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==", + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.13.tgz", + "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==", "dev": true, "license": "MIT", "peer": true, @@ -6577,17 +6984,97 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/compat": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.9.tgz", + "integrity": "sha512-gCdSY54n7k+driCadyMNv8JSPzYLeDVM/ikZRtvtROBpRdFSkS8W9A82MqsaY7lZuwL0wiapgD0NT1xT0hyJsA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -6595,7 +7082,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6630,16 +7117,13 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6675,27 +7159,38 @@ "node": "*" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", + "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.13.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@figspec/components": { @@ -6796,44 +7291,42 @@ "@hapi/hoek": "^9.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "Apache-2.0", "engines": { - "node": "*" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/module-importer": { @@ -6850,13 +7343,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@inquirer/checkbox": { "version": "2.5.0", @@ -8126,6 +8625,66 @@ } } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.0.tgz", + "integrity": "sha512-m//7RlINx1F3sz3KqwY1WWzVgTcYX52HYk4bJ1hkBXV3zccAEth+jRvG8DBRrdaQuRsPAJOx2MH3zaHNCKL7Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@msgpack/msgpack": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", @@ -9897,6 +10456,16 @@ "encoding": "^0.1.13" } }, + "node_modules/@sigstore/sign/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@sigstore/sign/node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -10540,6 +11109,29 @@ } } }, + "node_modules/@storybook/builder-webpack5/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@storybook/builder-webpack5/node_modules/style-loader": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", @@ -10883,9 +11475,9 @@ } }, "node_modules/@swc/core": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.24.tgz", - "integrity": "sha512-MaQEIpfcEMzx3VWWopbofKJvaraqmL6HbLlw2bFZ7qYqYw3rkhM0cQVEgyzbHtTWwCwPMFZSC2DUbhlZgrMfLg==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.29.tgz", + "integrity": "sha512-g4mThMIpWbNhV8G2rWp5a5/Igv8/2UFRJx2yImrLGMgrDDYZIopqZ/z0jZxDgqNA1QDx93rpwNF7jGsxVWcMlA==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -10901,16 +11493,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.11.24", - "@swc/core-darwin-x64": "1.11.24", - "@swc/core-linux-arm-gnueabihf": "1.11.24", - "@swc/core-linux-arm64-gnu": "1.11.24", - "@swc/core-linux-arm64-musl": "1.11.24", - "@swc/core-linux-x64-gnu": "1.11.24", - "@swc/core-linux-x64-musl": "1.11.24", - "@swc/core-win32-arm64-msvc": "1.11.24", - "@swc/core-win32-ia32-msvc": "1.11.24", - "@swc/core-win32-x64-msvc": "1.11.24" + "@swc/core-darwin-arm64": "1.11.29", + "@swc/core-darwin-x64": "1.11.29", + "@swc/core-linux-arm-gnueabihf": "1.11.29", + "@swc/core-linux-arm64-gnu": "1.11.29", + "@swc/core-linux-arm64-musl": "1.11.29", + "@swc/core-linux-x64-gnu": "1.11.29", + "@swc/core-linux-x64-musl": "1.11.29", + "@swc/core-win32-arm64-msvc": "1.11.29", + "@swc/core-win32-ia32-msvc": "1.11.29", + "@swc/core-win32-x64-msvc": "1.11.29" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -10922,9 +11514,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.24.tgz", - "integrity": "sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.29.tgz", + "integrity": "sha512-whsCX7URzbuS5aET58c75Dloby3Gtj/ITk2vc4WW6pSDQKSPDuONsIcZ7B2ng8oz0K6ttbi4p3H/PNPQLJ4maQ==", "cpu": [ "arm64" ], @@ -10939,9 +11531,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.24.tgz", - "integrity": "sha512-H/3cPs8uxcj2Fe3SoLlofN5JG6Ny5bl8DuZ6Yc2wr7gQFBmyBkbZEz+sPVgsID7IXuz7vTP95kMm1VL74SO5AQ==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.29.tgz", + "integrity": "sha512-S3eTo/KYFk+76cWJRgX30hylN5XkSmjYtCBnM4jPLYn7L6zWYEPajsFLmruQEiTEDUg0gBEWLMNyUeghtswouw==", "cpu": [ "x64" ], @@ -10956,9 +11548,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.24.tgz", - "integrity": "sha512-PHJgWEpCsLo/NGj+A2lXZ2mgGjsr96ULNW3+T3Bj2KTc8XtMUkE8tmY2Da20ItZOvPNC/69KroU7edyo1Flfbw==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.29.tgz", + "integrity": "sha512-o9gdshbzkUMG6azldHdmKklcfrcMx+a23d/2qHQHPDLUPAN+Trd+sDQUYArK5Fcm7TlpG4sczz95ghN0DMkM7g==", "cpu": [ "arm" ], @@ -10973,9 +11565,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.24.tgz", - "integrity": "sha512-C2FJb08+n5SD4CYWCTZx1uR88BN41ZieoHvI8A55hfVf2woT8+6ZiBzt74qW2g+ntZ535Jts5VwXAKdu41HpBg==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.29.tgz", + "integrity": "sha512-sLoaciOgUKQF1KX9T6hPGzvhOQaJn+3DHy4LOHeXhQqvBgr+7QcZ+hl4uixPKTzxk6hy6Hb0QOvQEdBAAR1gXw==", "cpu": [ "arm64" ], @@ -10990,9 +11582,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.24.tgz", - "integrity": "sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.29.tgz", + "integrity": "sha512-PwjB10BC0N+Ce7RU/L23eYch6lXFHz7r3NFavIcwDNa/AAqywfxyxh13OeRy+P0cg7NDpWEETWspXeI4Ek8otw==", "cpu": [ "arm64" ], @@ -11007,9 +11599,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.24.tgz", - "integrity": "sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.29.tgz", + "integrity": "sha512-i62vBVoPaVe9A3mc6gJG07n0/e7FVeAvdD9uzZTtGLiuIfVfIBta8EMquzvf+POLycSk79Z6lRhGPZPJPYiQaA==", "cpu": [ "x64" ], @@ -11024,9 +11616,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.24.tgz", - "integrity": "sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.29.tgz", + "integrity": "sha512-YER0XU1xqFdK0hKkfSVX1YIyCvMDI7K07GIpefPvcfyNGs38AXKhb2byySDjbVxkdl4dycaxxhRyhQ2gKSlsFQ==", "cpu": [ "x64" ], @@ -11041,9 +11633,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.24.tgz", - "integrity": "sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.29.tgz", + "integrity": "sha512-po+WHw+k9g6FAg5IJ+sMwtA/fIUL3zPQ4m/uJgONBATCVnDDkyW6dBA49uHNVtSEvjvhuD8DVWdFP847YTcITw==", "cpu": [ "arm64" ], @@ -11058,9 +11650,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.24.tgz", - "integrity": "sha512-9pKLIisE/Hh2vJhGIPvSoTK4uBSPxNVyXHmOrtdDot4E1FUUI74Vi8tFdlwNbaj8/vusVnb8xPXsxF1uB0VgiQ==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.29.tgz", + "integrity": "sha512-h+NjOrbqdRBYr5ItmStmQt6x3tnhqgwbj9YxdGPepbTDamFv7vFnhZR0YfB3jz3UKJ8H3uGJ65Zw1VsC+xpFkg==", "cpu": [ "ia32" ], @@ -11075,9 +11667,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.24.tgz", - "integrity": "sha512-sybnXtOsdB+XvzVFlBVGgRHLqp3yRpHK7CrmpuDKszhj/QhmsaZzY/GHSeALlMtLup13M0gqbcQvsTNlAHTg3w==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.29.tgz", + "integrity": "sha512-Q8cs2BDV9wqDvqobkXOYdC+pLUSEpX/KvI0Dgfun1F+LzuLotRFuDhrvkU9ETJA6OnD2+Fn/ieHgloiKA/Mn/g==", "cpu": [ "x64" ], @@ -11505,9 +12097,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, @@ -11827,9 +12419,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.17.16", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", - "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", + "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", "dev": true, "license": "MIT" }, @@ -11928,6 +12520,29 @@ "node": ">= 6" } }, + "node_modules/@types/node-fetch/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@types/node-fetch/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@types/node-forge": { "version": "1.3.11", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", @@ -12709,13 +13324,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, "node_modules/@unrs/resolver-binding-darwin-arm64": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz", @@ -13524,13 +14132,14 @@ } }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" @@ -15099,87 +15708,24 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/body-parser/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, "node_modules/bonjour-service": { @@ -15713,6 +16259,27 @@ "node": ">= 6.0.0" } }, + "node_modules/cache-content-type/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cache-content-type/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -16357,6 +16924,76 @@ "node": ">=8.0.0" } }, + "node_modules/co-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co-body/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/co-body/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/co-body/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/co-body/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/co-body/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/code-block-writer": { "version": "13.0.3", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", @@ -16812,9 +17449,10 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -16839,9 +17477,9 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -16849,11 +17487,14 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cookies": { "version": "0.9.1", @@ -18008,16 +18649,16 @@ } }, "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, "node_modules/dom-accessibility-api": { @@ -18444,9 +19085,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.155", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", - "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", + "version": "1.5.157", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz", + "integrity": "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==", "dev": true, "license": "ISC" }, @@ -18497,9 +19138,9 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "20.17.48", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.48.tgz", - "integrity": "sha512-KpSfKOHPsiSC4IkZeu2LsusFwExAIVGkhG1KkbaBMLwau0uMhj0fCrvyg9ddM2sAvd+gtiBJLir4LAw1MNMIaw==", + "version": "20.17.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.50.tgz", + "integrity": "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A==", "dev": true, "license": "MIT", "dependencies": { @@ -18552,9 +19193,10 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -18699,9 +19341,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "version": "1.23.10", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.10.tgz", + "integrity": "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==", "dev": true, "license": "MIT", "dependencies": { @@ -18709,18 +19351,18 @@ "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -18736,13 +19378,13 @@ "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", + "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", + "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", @@ -18755,7 +19397,7 @@ "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -18990,60 +19632,66 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz", + "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.13.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.26.0", + "@eslint/plugin-kit": "^0.2.8", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.2", + "@modelcontextprotocol/sdk": "^1.8.0", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "zod": "^3.24.2" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { @@ -19214,19 +19862,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-import/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -19383,39 +20018,19 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -19446,32 +20061,32 @@ "node": "*" } }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -19607,6 +20222,16 @@ "node": ">=12.0.0" } }, + "node_modules/eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -19715,192 +20340,82 @@ "license": "Apache-2.0" }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "node_modules/express/node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "dev": true, "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { "node": ">= 0.8" } }, - "node_modules/express/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/express/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/express/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -20160,16 +20675,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/filelist": { @@ -20236,6 +20751,16 @@ "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -20243,6 +20768,29 @@ "dev": true, "license": "MIT" }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/find-cache-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", @@ -20355,81 +20903,17 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/flat-cache/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flat-cache/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16" } }, "node_modules/flatted": { @@ -20729,6 +21213,27 @@ "node": ">= 6" } }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -20769,12 +21274,13 @@ "license": "ISC" }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/from": { @@ -21145,9 +21651,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -21937,6 +22443,15 @@ "node": ">= 0.6" } }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/http-auth": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-4.1.9.tgz", @@ -22003,15 +22518,6 @@ "node": ">= 0.8" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/http-parser-js": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", @@ -22566,13 +23072,13 @@ } }, "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">= 0.10" } }, "node_modules/is-arguments": { @@ -22985,16 +23491,6 @@ "node": ">=8" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -23469,9 +23965,9 @@ } }, "node_modules/jackspeak": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", - "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -25643,6 +26139,49 @@ "node": ">=8.0.0" } }, + "node_modules/koa-bodyparser/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-bodyparser/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-bodyparser/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-bodyparser/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/koa-compose": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", @@ -25678,6 +26217,49 @@ "streaming-json-stringify": "3" } }, + "node_modules/koa/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/koa/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/koa/node_modules/http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", @@ -25703,6 +26285,67 @@ "node": ">= 0.6" } }, + "node_modules/koa/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/launch-editor": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", @@ -26967,6 +27610,16 @@ "node": ">=8" } }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/make-fetch-happen/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -27259,12 +27912,13 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/memfs": { @@ -27291,11 +27945,14 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -27957,21 +28614,23 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" @@ -28351,6 +29010,19 @@ "dev": true, "license": "MIT" }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", @@ -28404,6 +29076,7 @@ "version": "1.4.5-lts.2", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -28418,6 +29091,36 @@ "node": ">= 6.0.0" } }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/multer/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -28430,6 +29133,19 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/multicast-dns": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", @@ -28582,9 +29298,10 @@ } }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -28966,6 +29683,16 @@ "encoding": "^0.1.13" } }, + "node_modules/node-gyp/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-gyp/node_modules/nopt": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", @@ -29462,6 +30189,16 @@ "encoding": "^0.1.13" } }, + "node_modules/npm-registry-fetch/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/npm-registry-fetch/node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -30202,9 +30939,9 @@ } }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -31194,6 +31931,16 @@ "nice-napi": "^1.0.2" } }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/pkg-dir": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", @@ -32052,16 +32799,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -32227,32 +32964,21 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.6.3", "unpipe": "1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -33136,6 +33862,47 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -33545,72 +34312,6 @@ "node": ">= 18" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/serialize-error": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", @@ -33671,6 +34372,20 @@ "node": ">= 0.8.0" } }, + "node_modules/serve-index/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-index/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -33714,6 +34429,29 @@ "dev": true, "license": "ISC" }, + "node_modules/serve-index/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-index/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -33721,6 +34459,16 @@ "dev": true, "license": "MIT" }, + "node_modules/serve-index/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-index/node_modules/setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", @@ -33728,118 +34476,30 @@ "dev": true, "license": "ISC" }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "dev": true, "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/serve-static/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" + "node": ">= 18" } }, "node_modules/set-blocking": { @@ -34610,12 +35270,12 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/steno": { @@ -35309,9 +35969,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", - "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", "dev": true, "license": "MIT", "dependencies": { @@ -35551,13 +36211,6 @@ "node": "*" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -36244,6 +36897,16 @@ "encoding": "^0.1.13" } }, + "node_modules/tuf-js/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/tuf-js/node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -36360,13 +37023,15 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" @@ -38024,17 +38689,27 @@ "url": "https://github.com/sponsors/streamich" } }, - "node_modules/webpack-dev-middleware/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "mime-db": "1.52.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/webpack-dev-server": { @@ -38120,6 +38795,45 @@ "@types/send": "*" } }, + "node_modules/webpack-dev-server/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/webpack-dev-server/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -38145,6 +38859,53 @@ "fsevents": "~2.3.2" } }, + "node_modules/webpack-dev-server/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -38158,6 +38919,82 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webpack-dev-server/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-server/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack-dev-server/node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -38196,6 +39033,29 @@ } } }, + "node_modules/webpack-dev-server/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/webpack-dev-server/node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -38225,6 +39085,72 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webpack-dev-server/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack-dev-server/node_modules/open": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", @@ -38244,6 +39170,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webpack-dev-server/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, "node_modules/webpack-dev-server/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -38257,6 +39190,38 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/webpack-dev-server/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/webpack-dev-server/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/webpack-dev-server/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -38270,6 +39235,71 @@ "node": ">=8.10.0" } }, + "node_modules/webpack-dev-server/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack-hot-middleware": { "version": "2.26.1", "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.26.1.tgz", @@ -38346,13 +39376,6 @@ "dev": true, "license": "MIT" }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/webpack/node_modules/browserslist": { "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", @@ -38417,6 +39440,29 @@ "dev": true, "license": "MIT" }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -38982,6 +40028,26 @@ "node": "*" } }, + "node_modules/zod": { + "version": "3.25.23", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.23.tgz", + "integrity": "sha512-Od2bdMosahjSrSgJtakrwjMDb1zM1A3VIHCPGveZt/3/wlrTWBya2lmEh2OYe4OIu8mPTmmr0gnLHIWQXdtWBg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, "node_modules/zone.js": { "version": "0.14.10", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", diff --git a/package.json b/package.json index fe7f69ff40d..84164696f46 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@compodoc/compodoc": "1.1.26", "@electron/notarize": "2.5.0", "@electron/rebuild": "3.7.2", + "@eslint/compat": "1.2.9", "@lit-labs/signals": "0.1.2", "@ngtools/webpack": "18.2.19", "@storybook/addon-a11y": "8.6.12", @@ -102,7 +103,7 @@ "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.6.4", - "eslint": "8.57.1", + "eslint": "9.26.0", "eslint-config-prettier": "10.1.2", "eslint-import-resolver-typescript": "4.3.4", "eslint-plugin-import": "2.31.0", @@ -207,6 +208,12 @@ "zxcvbn": "4.4.2" }, "overrides": { + "eslint-plugin-rxjs": { + "eslint": "$eslint" + }, + "eslint-plugin-rxjs-angular": { + "eslint": "$eslint" + }, "tailwindcss": "$tailwindcss", "@storybook/angular": { "zone.js": "$zone.js" From 88bc7625218028cdea3d73663a00f8d24133ba6e Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Tue, 27 May 2025 14:51:40 -0400 Subject: [PATCH 35/41] PM-16645 (#14649) --- .../src/autofill/services/autofill.service.ts | 248 +----------------- libs/common/src/enums/feature-flag.enum.ts | 2 - 2 files changed, 1 insertion(+), 249 deletions(-) diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 525010bacc1..fdd881c2760 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -1579,252 +1579,6 @@ export default class AutofillService implements AutofillServiceInterface { return [expectedDateFormat, dateFormatPatterns]; } - /** - * Generates the autofill script for the specified page details and identify cipher item. - * @param {AutofillScript} fillScript - * @param {AutofillPageDetails} pageDetails - * @param {{[p: string]: AutofillField}} filledFields - * @param {GenerateFillScriptOptions} options - * @returns {AutofillScript} - * @private - */ - private async generateIdentityFillScript( - fillScript: AutofillScript, - pageDetails: AutofillPageDetails, - filledFields: { [id: string]: AutofillField }, - options: GenerateFillScriptOptions, - ): Promise { - if (await this.configService.getFeatureFlag(FeatureFlag.GenerateIdentityFillScriptRefactor)) { - return this._generateIdentityFillScript(fillScript, pageDetails, filledFields, options); - } - - if (!options.cipher.identity) { - return null; - } - - const fillFields: { [id: string]: AutofillField } = {}; - - pageDetails.fields.forEach((f) => { - if ( - AutofillService.isExcludedFieldType(f, AutoFillConstants.ExcludedAutofillTypes) || - ["current-password", "new-password"].includes(f.autoCompleteType) - ) { - return; - } - - for (let i = 0; i < IdentityAutoFillConstants.IdentityAttributes.length; i++) { - const attr = IdentityAutoFillConstants.IdentityAttributes[i]; - // eslint-disable-next-line - if (!f.hasOwnProperty(attr) || !f[attr] || !f.viewable) { - continue; - } - - // ref https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill - // ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/ - if ( - !fillFields.name && - AutofillService.isFieldMatch( - f[attr], - IdentityAutoFillConstants.FullNameFieldNames, - IdentityAutoFillConstants.FullNameFieldNameValues, - ) - ) { - fillFields.name = f; - break; - } else if ( - !fillFields.firstName && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames) - ) { - fillFields.firstName = f; - break; - } else if ( - !fillFields.middleName && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames) - ) { - fillFields.middleName = f; - break; - } else if ( - !fillFields.lastName && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames) - ) { - fillFields.lastName = f; - break; - } else if ( - !fillFields.title && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames) - ) { - fillFields.title = f; - break; - } else if ( - !fillFields.email && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames) - ) { - fillFields.email = f; - break; - } else if ( - !fillFields.address1 && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames) - ) { - fillFields.address1 = f; - break; - } else if ( - !fillFields.address2 && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames) - ) { - fillFields.address2 = f; - break; - } else if ( - !fillFields.address3 && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames) - ) { - fillFields.address3 = f; - break; - } else if ( - !fillFields.address && - AutofillService.isFieldMatch( - f[attr], - IdentityAutoFillConstants.AddressFieldNames, - IdentityAutoFillConstants.AddressFieldNameValues, - ) - ) { - fillFields.address = f; - break; - } else if ( - !fillFields.postalCode && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames) - ) { - fillFields.postalCode = f; - break; - } else if ( - !fillFields.city && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames) - ) { - fillFields.city = f; - break; - } else if ( - !fillFields.state && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames) - ) { - fillFields.state = f; - break; - } else if ( - !fillFields.country && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames) - ) { - fillFields.country = f; - break; - } else if ( - !fillFields.phone && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames) - ) { - fillFields.phone = f; - break; - } else if ( - !fillFields.username && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames) - ) { - fillFields.username = f; - break; - } else if ( - !fillFields.company && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames) - ) { - fillFields.company = f; - break; - } - } - }); - - const identity = options.cipher.identity; - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "title"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "firstName"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "middleName"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "lastName"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address1"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address2"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address3"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "city"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "postalCode"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "company"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "email"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "phone"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "username"); - - let filledState = false; - if (fillFields.state && identity.state && identity.state.length > 2) { - const stateLower = identity.state.toLowerCase(); - const isoState = - IdentityAutoFillConstants.IsoStates[stateLower] || - IdentityAutoFillConstants.IsoProvinces[stateLower]; - if (isoState) { - filledState = true; - this.makeScriptActionWithValue(fillScript, isoState, fillFields.state, filledFields); - } - } - - if (!filledState) { - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "state"); - } - - let filledCountry = false; - if (fillFields.country && identity.country && identity.country.length > 2) { - const countryLower = identity.country.toLowerCase(); - const isoCountry = IdentityAutoFillConstants.IsoCountries[countryLower]; - if (isoCountry) { - filledCountry = true; - this.makeScriptActionWithValue(fillScript, isoCountry, fillFields.country, filledFields); - } - } - - if (!filledCountry) { - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "country"); - } - - if (fillFields.name && (identity.firstName || identity.lastName)) { - let fullName = ""; - if (AutofillService.hasValue(identity.firstName)) { - fullName = identity.firstName; - } - if (AutofillService.hasValue(identity.middleName)) { - if (fullName !== "") { - fullName += " "; - } - fullName += identity.middleName; - } - if (AutofillService.hasValue(identity.lastName)) { - if (fullName !== "") { - fullName += " "; - } - fullName += identity.lastName; - } - - this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields); - } - - if (fillFields.address && AutofillService.hasValue(identity.address1)) { - let address = ""; - if (AutofillService.hasValue(identity.address1)) { - address = identity.address1; - } - if (AutofillService.hasValue(identity.address2)) { - if (address !== "") { - address += ", "; - } - address += identity.address2; - } - if (AutofillService.hasValue(identity.address3)) { - if (address !== "") { - address += ", "; - } - address += identity.address3; - } - - this.makeScriptActionWithValue(fillScript, address, fillFields.address, filledFields); - } - - return fillScript; - } - /** * Generates the autofill script for the specified page details and identity cipher item. * @@ -1833,7 +1587,7 @@ export default class AutofillService implements AutofillServiceInterface { * @param filledFields - The fields that have already been filled, passed between method references * @param options - Contains data used to fill cipher items */ - private _generateIdentityFillScript( + private generateIdentityFillScript( fillScript: AutofillScript, pageDetails: AutofillPageDetails, filledFields: { [id: string]: AutofillField }, diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index a5c5c615e70..8cd44f9d627 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -22,7 +22,6 @@ export enum FeatureFlag { BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", - GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", IdpAutoSubmitLogin = "idp-auto-submit-login", NotificationRefresh = "notification-refresh", UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", @@ -87,7 +86,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, - [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, [FeatureFlag.IdpAutoSubmitLogin]: FALSE, [FeatureFlag.NotificationRefresh]: FALSE, [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, From cb770f5cd3381b38d34d502fb26f07659cf02d49 Mon Sep 17 00:00:00 2001 From: Patrick-Pimentel-Bitwarden Date: Tue, 27 May 2025 16:01:07 -0400 Subject: [PATCH 36/41] refactor(browser-platform-utils): Remove Deprecation and Fix Code (#14709) * refactor(browser-platform-utils): Remove Deprecation and Fix Code - Changed usages of firefox to private and moved the usages to the preferred public method and removed the deprecations. * fix(browser-platform-utils): Remove Deprecation and Fix Code - Tiny changes. * test(browser-platform-utils): Remove Deprecation and Fix Code - Fixed up test --- .../src/platform/listeners/update-badge.ts | 10 +++---- .../browser-platform-utils.service.spec.ts | 3 +- .../browser-platform-utils.service.ts | 30 ++++--------------- 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/apps/browser/src/platform/listeners/update-badge.ts b/apps/browser/src/platform/listeners/update-badge.ts index cd74b9ebf7d..c168ae44f3c 100644 --- a/apps/browser/src/platform/listeners/update-badge.ts +++ b/apps/browser/src/platform/listeners/update-badge.ts @@ -7,13 +7,13 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import MainBackground from "../../background/main.background"; import IconDetails from "../../vault/background/models/icon-details"; import { BrowserApi } from "../browser/browser-api"; -import { BrowserPlatformUtilsService } from "../services/platform-utils/browser-platform-utils.service"; export type BadgeOptions = { tab?: chrome.tabs.Tab; @@ -28,6 +28,7 @@ export class UpdateBadge { private badgeAction: typeof chrome.action | typeof chrome.browserAction; private sidebarAction: OperaSidebarAction | FirefoxSidebarAction; private win: Window & typeof globalThis; + private platformUtilsService: PlatformUtilsService; constructor(win: Window & typeof globalThis, services: MainBackground) { this.badgeAction = BrowserApi.getBrowserAction(); @@ -38,6 +39,7 @@ export class UpdateBadge { this.authService = services.authService; this.cipherService = services.cipherService; this.accountService = services.accountService; + this.platformUtilsService = services.platformUtilsService; } async run(opts?: { tabId?: number; windowId?: number }): Promise { @@ -129,7 +131,7 @@ export class UpdateBadge { 38: "/images/icon38" + iconSuffix + ".png", }, }; - if (windowId && BrowserPlatformUtilsService.isFirefox()) { + if (windowId && this.platformUtilsService.isFirefox()) { options.windowId = windowId; } @@ -204,9 +206,7 @@ export class UpdateBadge { } private get useSyncApiCalls() { - return ( - BrowserPlatformUtilsService.isFirefox() || BrowserPlatformUtilsService.isSafari(this.win) - ); + return this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari(); } private isOperaSidebar( diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts index 38166d10a08..f75e9cc29a5 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts @@ -126,12 +126,11 @@ describe("Browser Utils Service", () => { configurable: true, value: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0", }); - jest.spyOn(BrowserPlatformUtilsService, "isFirefox"); browserPlatformUtilsService.getDevice(); expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension); - expect(BrowserPlatformUtilsService.isFirefox).toHaveBeenCalledTimes(1); + expect(browserPlatformUtilsService.isFirefox()).toBe(true); }); }); diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts index 22708d8e425..4ae412fbda6 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts @@ -60,10 +60,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return ClientType.Browser; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ - static isFirefox(): boolean { + private static isFirefox(): boolean { return ( navigator.userAgent.indexOf(" Firefox/") !== -1 || navigator.userAgent.indexOf(" Gecko/") !== -1 @@ -74,9 +71,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.FirefoxExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isChrome(globalContext: Window | ServiceWorkerGlobalScope): boolean { return globalContext.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1; } @@ -85,9 +79,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.ChromeExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isEdge(): boolean { return navigator.userAgent.indexOf(" Edg/") !== -1; } @@ -96,9 +87,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.EdgeExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isOpera(globalContext: Window | ServiceWorkerGlobalScope): boolean { return ( !!globalContext.opr?.addons || @@ -111,9 +99,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.OperaExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isVivaldi(): boolean { return navigator.userAgent.indexOf(" Vivaldi/") !== -1; } @@ -122,10 +107,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.VivaldiExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ - static isSafari(globalContext: Window | ServiceWorkerGlobalScope): boolean { + private static isSafari(globalContext: Window | ServiceWorkerGlobalScope): boolean { // Opera masquerades as Safari, so make sure we're not there first return ( !BrowserPlatformUtilsService.isOpera(globalContext) && @@ -137,6 +119,10 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return navigator.userAgent.match("Version/([0-9.]*)")?.[1]; } + isSafari(): boolean { + return this.getDevice() === DeviceType.SafariExtension; + } + /** * Safari previous to version 16.1 had a bug which caused artifacts on hover in large extension popups. * https://bugs.webkit.org/show_bug.cgi?id=218704 @@ -151,10 +137,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return parts?.[0] < 16 || (parts?.[0] === 16 && parts?.[1] === 0); } - isSafari(): boolean { - return this.getDevice() === DeviceType.SafariExtension; - } - isIE(): boolean { return false; } From 50143a4b88dc170de92dd603b507545c334f2227 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Tue, 27 May 2025 16:50:39 -0400 Subject: [PATCH 37/41] pushes search text to a subject (#14880) use distinctUntilChanged to prevent duplicate filtering operations Run filtering outside angular zone to prevent change detection issues --- .../vault-search/vault-v2-search.component.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts index 32f5611f436..b68818454d1 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts @@ -1,8 +1,8 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, NgZone } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; -import { Subject, Subscription, debounceTime, filter } from "rxjs"; +import { Subject, Subscription, debounceTime, distinctUntilChanged, filter } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchModule } from "@bitwarden/components"; @@ -22,13 +22,16 @@ export class VaultV2SearchComponent { private searchText$ = new Subject(); - constructor(private vaultPopupItemsService: VaultPopupItemsService) { + constructor( + private vaultPopupItemsService: VaultPopupItemsService, + private ngZone: NgZone, + ) { this.subscribeToLatestSearchText(); this.subscribeToApplyFilter(); } onSearchTextChanged() { - this.vaultPopupItemsService.applyFilter(this.searchText); + this.searchText$.next(this.searchText); } subscribeToLatestSearchText(): Subscription { @@ -44,9 +47,13 @@ export class VaultV2SearchComponent { subscribeToApplyFilter(): Subscription { return this.searchText$ - .pipe(debounceTime(SearchTextDebounceInterval), takeUntilDestroyed()) + .pipe(debounceTime(SearchTextDebounceInterval), distinctUntilChanged(), takeUntilDestroyed()) .subscribe((data) => { - this.vaultPopupItemsService.applyFilter(data); + this.ngZone.runOutsideAngular(() => { + this.ngZone.run(() => { + this.vaultPopupItemsService.applyFilter(data); + }); + }); }); } } From 4fcc4793bbfff10fd3d2a228be004d28ae5eb838 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Wed, 28 May 2025 09:41:56 +1000 Subject: [PATCH 38/41] Add additional jsdoc to policyservice (#14934) --- .../policy/policy.service.abstraction.ts | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts b/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts index 68f9843c5bd..bf02872ed7c 100644 --- a/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts @@ -7,6 +7,9 @@ import { MasterPasswordPolicyOptions } from "../../models/domain/master-password import { Policy } from "../../models/domain/policy"; import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options"; +/** + * The primary service for retrieving and evaluating policies from sync data. + */ export abstract class PolicyService { /** * All policies for the provided user from sync data. @@ -24,7 +27,7 @@ export abstract class PolicyService { abstract policiesByType$: (policyType: PolicyType, userId: UserId) => Observable; /** - * @returns true if a policy of the specified type applies to the specified user, otherwise false. + * @returns true if any policy of the specified type applies to the specified user, otherwise false. * A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner). * This does not take into account the policy's configuration - if that is important, use {@link policiesByType$} to get the * {@link Policy} objects and then filter by Policy.data. @@ -35,8 +38,12 @@ export abstract class PolicyService { /** * Combines all Master Password policies that apply to the user. + * If you are evaluating Master Password policies before the first sync has completed, + * you must supply your own `policies` value. + * @param userId The user against whom the policy needs to be enforced. + * @param policies The policies to be evaluated; if null or undefined, it will default to using policies from sync data. * @returns a set of options which represent the minimum Master Password settings that the user must - * comply with in order to comply with **all** Master Password policies. + * comply with in order to comply with **all** applicable Master Password policies. */ abstract masterPasswordPolicyOptions$: ( userId: UserId, @@ -62,7 +69,17 @@ export abstract class PolicyService { ) => [ResetPasswordPolicyOptions, boolean]; } +/** + * An "internal" extension of the `PolicyService` which allows the update of policy data in the local sync data. + * This does not update any policies on the server. + */ export abstract class InternalPolicyService extends PolicyService { + /** + * Upsert a policy in the local sync data. This does not update any policies on the server. + */ abstract upsert: (policy: PolicyData, userId: UserId) => Promise; + /** + * Replace a policy in the local sync data. This does not update any policies on the server. + */ abstract replace: (policies: { [id: string]: PolicyData }, userId: UserId) => Promise; } From d1fb37d696b98c64de0a291af868a56db99788cd Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 28 May 2025 15:00:30 +0200 Subject: [PATCH 39/41] [PM-17635] [PM-18601] Simplifying mocking and usage of the sdk (#14287) * feat: add our own custom deep mocker * feat: use new mock service in totp tests * feat: implement userClient mocking * chore: move mock files * feat: replace existing manual sdkService mocking * chore: rename to 'client' * chore: improve docs * feat: refactor sdkService to never return undefined BitwardenClient --- .../platform/abstractions/sdk/sdk.service.ts | 9 +- .../services/sdk/default-sdk.service.spec.ts | 8 +- .../services/sdk/default-sdk.service.ts | 5 +- .../src/platform/spec/mock-deep.spec.ts | 58 ++++ libs/common/src/platform/spec/mock-deep.ts | 271 ++++++++++++++++++ .../src/platform/spec/mock-sdk.service.ts | 81 ++++++ .../src/vault/services/totp.service.spec.ts | 31 +- .../src/services/import.service.spec.ts | 10 +- ...symmetric-key-regeneration.service.spec.ts | 25 +- 9 files changed, 444 insertions(+), 54 deletions(-) create mode 100644 libs/common/src/platform/spec/mock-deep.spec.ts create mode 100644 libs/common/src/platform/spec/mock-deep.ts create mode 100644 libs/common/src/platform/spec/mock-sdk.service.ts diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index 07dfb2aa0df..d629e4fe9fa 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -53,15 +53,18 @@ export abstract class SdkService { * This client can be used for operations that require a user context, such as retrieving ciphers * and operations involving crypto. It can also be used for operations that don't require a user context. * + * - If the user is not logged when the subscription is created, the observable will complete + * immediately with {@link UserNotLoggedInError}. + * - If the user is logged in, the observable will emit the client and complete whithout an error + * when the user logs out. + * * **WARNING:** Do not use `firstValueFrom(userClient$)`! Any operations on the client must be done within the observable. * The client will be destroyed when the observable is no longer subscribed to. * Please let platform know if you need a client that is not destroyed when the observable is no longer subscribed to. * * @param userId The user id for which to retrieve the client - * - * @throws {UserNotLoggedInError} If the user is not logged in */ - abstract userClient$(userId: UserId): Observable | undefined>; + abstract userClient$(userId: UserId): Observable>; /** * This method is used during/after an authentication procedure to set a new client for a specific user. diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts index 6531be58f05..70a08257471 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -132,15 +132,13 @@ describe("DefaultSdkService", () => { ); keyService.userKey$.calledWith(userId).mockReturnValue(userKey$); - const subject = new BehaviorSubject | undefined>(undefined); - service.userClient$(userId).subscribe(subject); - await new Promise(process.nextTick); + const userClientTracker = new ObservableTracker(service.userClient$(userId), false); + await userClientTracker.pauseUntilReceived(1); userKey$.next(undefined); - await new Promise(process.nextTick); + await userClientTracker.expectCompletion(); expect(mockClient.free).toHaveBeenCalledTimes(1); - expect(subject.value).toBe(undefined); }); }); diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 8e84642fb99..6be89a4b376 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -71,7 +71,7 @@ export class DefaultSdkService implements SdkService { private userAgent: string | null = null, ) {} - userClient$(userId: UserId): Observable | undefined> { + userClient$(userId: UserId): Observable> { return this.sdkClientOverrides.pipe( takeWhile((clients) => clients[userId] !== UnsetClient, false), map((clients) => { @@ -88,6 +88,7 @@ export class DefaultSdkService implements SdkService { return this.internalClient$(userId); }), + takeWhile((client) => client !== undefined, false), throwIfEmpty(() => new UserNotLoggedInError(userId)), ); } @@ -112,7 +113,7 @@ export class DefaultSdkService implements SdkService { * @param userId The user id for which to create the client * @returns An observable that emits the client for the user */ - private internalClient$(userId: UserId): Observable | undefined> { + private internalClient$(userId: UserId): Observable> { const cached = this.sdkClientCache.get(userId); if (cached !== undefined) { return cached; diff --git a/libs/common/src/platform/spec/mock-deep.spec.ts b/libs/common/src/platform/spec/mock-deep.spec.ts new file mode 100644 index 00000000000..535e02c11dd --- /dev/null +++ b/libs/common/src/platform/spec/mock-deep.spec.ts @@ -0,0 +1,58 @@ +import { mockDeep } from "./mock-deep"; + +class ToBeMocked { + property = "value"; + + method() { + return "method"; + } + + sub() { + return new SubToBeMocked(); + } +} + +class SubToBeMocked { + subProperty = "subValue"; + + sub() { + return new SubSubToBeMocked(); + } +} + +class SubSubToBeMocked { + subSubProperty = "subSubValue"; +} + +describe("deepMock", () => { + it("can mock properties", () => { + const mock = mockDeep(); + mock.property.replaceProperty("mocked value"); + expect(mock.property).toBe("mocked value"); + }); + + it("can mock methods", () => { + const mock = mockDeep(); + mock.method.mockReturnValue("mocked method"); + expect(mock.method()).toBe("mocked method"); + }); + + it("can mock sub-properties", () => { + const mock = mockDeep(); + mock.sub.mockDeep().subProperty.replaceProperty("mocked sub value"); + expect(mock.sub().subProperty).toBe("mocked sub value"); + }); + + it("can mock sub-sub-properties", () => { + const mock = mockDeep(); + mock.sub.mockDeep().sub.mockDeep().subSubProperty.replaceProperty("mocked sub-sub value"); + expect(mock.sub().sub().subSubProperty).toBe("mocked sub-sub value"); + }); + + it("returns the same mock object when calling mockDeep multiple times", () => { + const mock = mockDeep(); + const subMock1 = mock.sub.mockDeep(); + const subMock2 = mock.sub.mockDeep(); + expect(subMock1).toBe(subMock2); + }); +}); diff --git a/libs/common/src/platform/spec/mock-deep.ts b/libs/common/src/platform/spec/mock-deep.ts new file mode 100644 index 00000000000..89ef9a25451 --- /dev/null +++ b/libs/common/src/platform/spec/mock-deep.ts @@ -0,0 +1,271 @@ +// This is a modification of the code found in https://github.com/marchaos/jest-mock-extended +// to better support deep mocking of objects. + +// MIT License + +// Copyright (c) 2019 Marc McIntyre + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import { jest } from "@jest/globals"; +import { FunctionLike } from "jest-mock"; +import { calledWithFn, MatchersOrLiterals } from "jest-mock-extended"; +import { PartialDeep } from "type-fest"; + +type ProxiedProperty = string | number | symbol; + +export interface GlobalConfig { + // ignoreProps is required when we don't want to return anything for a mock (for example, when mocking a promise). + ignoreProps?: ProxiedProperty[]; +} + +const DEFAULT_CONFIG: GlobalConfig = { + ignoreProps: ["then"], +}; + +let GLOBAL_CONFIG = DEFAULT_CONFIG; + +export const JestMockExtended = { + DEFAULT_CONFIG, + configure: (config: GlobalConfig) => { + // Shallow merge so they can override anything they want. + GLOBAL_CONFIG = { ...DEFAULT_CONFIG, ...config }; + }, + resetConfig: () => { + GLOBAL_CONFIG = DEFAULT_CONFIG; + }, +}; + +export interface CalledWithMock extends jest.Mock { + calledWith: (...args: [...MatchersOrLiterals>]) => jest.Mock; +} + +export interface MockDeepMock { + mockDeep: () => DeepMockProxy; +} + +export interface ReplaceProperty { + /** + * mockDeep will by default return a jest.fn() for all properties, + * but this allows you to replace the property with a value. + * @param value The value to replace the property with. + */ + replaceProperty(value: T): void; +} + +export type _MockProxy = { + [K in keyof T]: T[K] extends FunctionLike ? T[K] & CalledWithMock : T[K]; +}; + +export type MockProxy = _MockProxy & T; + +export type _DeepMockProxy = { + // This supports deep mocks in the else branch + [K in keyof T]: T[K] extends (...args: infer A) => infer R + ? T[K] & CalledWithMock & MockDeepMock + : T[K] & ReplaceProperty & _DeepMockProxy; +}; + +// we intersect with T here instead of on the mapped type above to +// prevent immediate type resolution on a recursive type, this will +// help to improve performance for deeply nested recursive mocking +// at the same time, this intersection preserves private properties +export type DeepMockProxy = _DeepMockProxy & T; + +export type _DeepMockProxyWithFuncPropSupport = { + // This supports deep mocks in the else branch + [K in keyof T]: T[K] extends FunctionLike + ? CalledWithMock & DeepMockProxy + : DeepMockProxy; +}; + +export type DeepMockProxyWithFuncPropSupport = _DeepMockProxyWithFuncPropSupport & T; + +export interface MockOpts { + deep?: boolean; + fallbackMockImplementation?: (...args: any[]) => any; +} + +export const mockClear = (mock: MockProxy) => { + for (const key of Object.keys(mock)) { + if (mock[key] === null || mock[key] === undefined) { + continue; + } + + if (mock[key]._isMockObject) { + mockClear(mock[key]); + } + + if (mock[key]._isMockFunction) { + mock[key].mockClear(); + } + } + + // This is a catch for if they pass in a jest.fn() + if (!mock._isMockObject) { + return mock.mockClear(); + } +}; + +export const mockReset = (mock: MockProxy) => { + for (const key of Object.keys(mock)) { + if (mock[key] === null || mock[key] === undefined) { + continue; + } + + if (mock[key]._isMockObject) { + mockReset(mock[key]); + } + if (mock[key]._isMockFunction) { + mock[key].mockReset(); + } + } + + // This is a catch for if they pass in a jest.fn() + // Worst case, we will create a jest.fn() (since this is a proxy) + // below in the get and call mockReset on it + if (!mock._isMockObject) { + return mock.mockReset(); + } +}; + +export function mockDeep( + opts: { + funcPropSupport?: true; + fallbackMockImplementation?: MockOpts["fallbackMockImplementation"]; + }, + mockImplementation?: PartialDeep, +): DeepMockProxyWithFuncPropSupport; +export function mockDeep(mockImplementation?: PartialDeep): DeepMockProxy; +export function mockDeep(arg1: any, arg2?: any) { + const [opts, mockImplementation] = + typeof arg1 === "object" && + (typeof arg1.fallbackMockImplementation === "function" || arg1.funcPropSupport === true) + ? [arg1, arg2] + : [{}, arg1]; + return mock(mockImplementation, { + deep: true, + fallbackMockImplementation: opts.fallbackMockImplementation, + }); +} + +const overrideMockImp = (obj: PartialDeep, opts?: MockOpts) => { + const proxy = new Proxy>(obj, handler(opts)); + for (const name of Object.keys(obj)) { + if (typeof obj[name] === "object" && obj[name] !== null) { + proxy[name] = overrideMockImp(obj[name], opts); + } else { + proxy[name] = obj[name]; + } + } + + return proxy; +}; + +const handler = (opts?: MockOpts): ProxyHandler => ({ + ownKeys(target: MockProxy) { + return Reflect.ownKeys(target); + }, + + set: (obj: MockProxy, property: ProxiedProperty, value: any) => { + obj[property] = value; + return true; + }, + + get: (obj: MockProxy, property: ProxiedProperty) => { + const fn = calledWithFn({ fallbackMockImplementation: opts?.fallbackMockImplementation }); + + if (!(property in obj)) { + if (GLOBAL_CONFIG.ignoreProps?.includes(property)) { + return undefined; + } + // Jest's internal equality checking does some wierd stuff to check for iterable equality + if (property === Symbol.iterator) { + return obj[property]; + } + + if (property === "_deepMock") { + return obj[property]; + } + // So this calls check here is totally not ideal - jest internally does a + // check to see if this is a spy - which we want to say no to, but blindly returning + // an proxy for calls results in the spy check returning true. This is another reason + // why deep is opt in. + if (opts?.deep && property !== "calls") { + obj[property] = new Proxy>(fn, handler(opts)); + obj[property].replaceProperty = (value: T[K]) => { + obj[property] = value; + }; + obj[property].mockDeep = () => { + if (obj[property]._deepMock) { + return obj[property]._deepMock; + } + + const mock = mockDeep({ + fallbackMockImplementation: opts?.fallbackMockImplementation, + }); + (obj[property] as CalledWithMock).mockReturnValue(mock); + obj[property]._deepMock = mock; + return mock; + }; + obj[property]._isMockObject = true; + } else { + obj[property] = calledWithFn({ + fallbackMockImplementation: opts?.fallbackMockImplementation, + }); + } + } + + // @ts-expect-error Hack by author of jest-mock-extended + if (obj instanceof Date && typeof obj[property] === "function") { + // @ts-expect-error Hack by author of jest-mock-extended + return obj[property].bind(obj); + } + + return obj[property]; + }, +}); + +const mock = & T = MockProxy & T>( + mockImplementation: PartialDeep = {} as PartialDeep, + opts?: MockOpts, +): MockedReturn => { + // @ts-expect-error private + mockImplementation!._isMockObject = true; + return overrideMockImp(mockImplementation, opts); +}; + +export const mockFn = (): CalledWithMock & T => { + // @ts-expect-error Hack by author of jest-mock-extended + return calledWithFn(); +}; + +export const stub = (): T => { + return new Proxy({} as T, { + get: (obj, property: ProxiedProperty) => { + if (property in obj) { + // @ts-expect-error Hack by author of jest-mock-extended + return obj[property]; + } + return jest.fn(); + }, + }); +}; + +export default mock; diff --git a/libs/common/src/platform/spec/mock-sdk.service.ts b/libs/common/src/platform/spec/mock-sdk.service.ts new file mode 100644 index 00000000000..66a6ab3ec84 --- /dev/null +++ b/libs/common/src/platform/spec/mock-sdk.service.ts @@ -0,0 +1,81 @@ +import { + BehaviorSubject, + distinctUntilChanged, + map, + Observable, + takeWhile, + throwIfEmpty, +} from "rxjs"; + +import { BitwardenClient } from "@bitwarden/sdk-internal"; + +import { UserId } from "../../types/guid"; +import { SdkService, UserNotLoggedInError } from "../abstractions/sdk/sdk.service"; +import { Rc } from "../misc/reference-counting/rc"; + +import { DeepMockProxy, mockDeep } from "./mock-deep"; + +export class MockSdkService implements SdkService { + private userClients$ = new BehaviorSubject<{ + [userId: UserId]: Rc | undefined; + }>({}); + + private _client$ = new BehaviorSubject(mockDeep()); + client$ = this._client$.asObservable(); + + version$ = new BehaviorSubject("0.0.1-test").asObservable(); + + userClient$(userId: UserId): Observable> { + return this.userClients$.pipe( + takeWhile((clients) => clients[userId] !== undefined, false), + map((clients) => clients[userId] as Rc), + distinctUntilChanged(), + throwIfEmpty(() => new UserNotLoggedInError(userId)), + ); + } + + setClient(): void { + throw new Error("Not supported in mock service"); + } + + /** + * Returns the non-user scoped client mock. + * This is what is returned by the `client$` observable. + */ + get client(): DeepMockProxy { + return this._client$.value; + } + + readonly simulate = { + /** + * Simulates a user login, and returns a user-scoped mock for the user. + * This will be return by the `userClient$` observable. + * + * @param userId The userId to simulate login for. + * @returns A user-scoped mock for the user. + */ + userLogin: (userId: UserId) => { + const client = mockDeep(); + this.userClients$.next({ + ...this.userClients$.getValue(), + [userId]: new Rc(client), + }); + return client; + }, + + /** + * Simulates a user logout, and disposes the user-scoped mock for the user. + * This will remove the user-scoped mock from the `userClient$` observable. + * + * @param userId The userId to simulate logout for. + */ + userLogout: (userId: UserId) => { + const clients = this.userClients$.value; + clients[userId]?.markForDisposal(); + this.userClients$.next({ + ...clients, + [userId]: undefined, + }); + }, + }; +} diff --git a/libs/common/src/vault/services/totp.service.spec.ts b/libs/common/src/vault/services/totp.service.spec.ts index c653b4ce1db..4aca262d537 100644 --- a/libs/common/src/vault/services/totp.service.spec.ts +++ b/libs/common/src/vault/services/totp.service.spec.ts @@ -1,38 +1,27 @@ -import { mock } from "jest-mock-extended"; -import { of, take } from "rxjs"; +import { take } from "rxjs"; -import { BitwardenClient, TotpResponse } from "@bitwarden/sdk-internal"; +import { TotpResponse } from "@bitwarden/sdk-internal"; -import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; +import { MockSdkService } from "../../platform/spec/mock-sdk.service"; import { TotpService } from "./totp.service"; describe("TotpService", () => { - let totpService: TotpService; - let generateTotpMock: jest.Mock; - - const sdkService = mock(); + let totpService!: TotpService; + let sdkService!: MockSdkService; beforeEach(() => { - generateTotpMock = jest - .fn() - .mockReturnValueOnce({ + sdkService = new MockSdkService(); + sdkService.client.vault + .mockDeep() + .totp.mockDeep() + .generate_totp.mockReturnValueOnce({ code: "123456", period: 30, }) .mockReturnValueOnce({ code: "654321", period: 30 }) .mockReturnValueOnce({ code: "567892", period: 30 }); - const mockBitwardenClient = { - vault: () => ({ - totp: () => ({ - generate_totp: generateTotpMock, - }), - }), - }; - - sdkService.client$ = of(mockBitwardenClient as unknown as BitwardenClient); - totpService = new TotpService(sdkService); // TOTP is time-based, so we need to mock the current time diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index 30309a3d9c2..6c8656f4c1d 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -1,5 +1,4 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { of } from "rxjs"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports @@ -8,14 +7,13 @@ import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { MockSdkService } from "@bitwarden/common/platform/spec/mock-sdk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { KeyService } from "@bitwarden/key-management"; -import { BitwardenClient } from "@bitwarden/sdk-internal"; import { BitwardenPasswordProtectedImporter } from "../importers/bitwarden/bitwarden-password-protected-importer"; import { Importer } from "../importers/importer"; @@ -35,7 +33,7 @@ describe("ImportService", () => { let encryptService: MockProxy; let pinService: MockProxy; let accountService: MockProxy; - let sdkService: MockProxy; + let sdkService: MockSdkService; beforeEach(() => { cipherService = mock(); @@ -46,9 +44,7 @@ describe("ImportService", () => { keyService = mock(); encryptService = mock(); pinService = mock(); - const mockClient = mock(); - sdkService = mock(); - sdkService.client$ = of(mockClient, mockClient, mockClient); + sdkService = new MockSdkService(); importService = new ImportService( cipherService, diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts index 35cef914588..84d1dd7ad72 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts @@ -5,17 +5,17 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; +import { MockSdkService } from "@bitwarden/common/platform/spec/mock-sdk.service"; import { makeStaticByteArray, mockEnc } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { BitwardenClient, VerifyAsymmetricKeysResponse } from "@bitwarden/sdk-internal"; +import { VerifyAsymmetricKeysResponse } from "@bitwarden/sdk-internal"; import { KeyService } from "../../abstractions/key.service"; import { UserAsymmetricKeysRegenerationApiService } from "../abstractions/user-asymmetric-key-regeneration-api.service"; @@ -24,24 +24,17 @@ import { DefaultUserAsymmetricKeysRegenerationService } from "./default-user-asy function setupVerificationResponse( mockVerificationResponse: VerifyAsymmetricKeysResponse, - sdkService: MockProxy, + sdkService: MockSdkService, ) { const mockKeyPairResponse = { userPublicKey: "userPublicKey", userKeyEncryptedPrivateKey: "userKeyEncryptedPrivateKey", }; - sdkService.client$ = of({ - crypto: () => ({ - verify_asymmetric_keys: jest.fn().mockReturnValue(mockVerificationResponse), - make_key_pair: jest.fn().mockReturnValue(mockKeyPairResponse), - }), - free: jest.fn(), - echo: jest.fn(), - version: jest.fn(), - throw: jest.fn(), - catch: jest.fn(), - } as unknown as BitwardenClient); + sdkService.client.crypto + .mockDeep() + .verify_asymmetric_keys.mockReturnValue(mockVerificationResponse); + sdkService.client.crypto.mockDeep().make_key_pair.mockReturnValue(mockKeyPairResponse); } function setupUserKeyValidation( @@ -74,7 +67,7 @@ describe("regenerateIfNeeded", () => { let cipherService: MockProxy; let userAsymmetricKeysRegenerationApiService: MockProxy; let logService: MockProxy; - let sdkService: MockProxy; + let sdkService: MockSdkService; let apiService: MockProxy; let configService: MockProxy; let encryptService: MockProxy; @@ -84,7 +77,7 @@ describe("regenerateIfNeeded", () => { cipherService = mock(); userAsymmetricKeysRegenerationApiService = mock(); logService = mock(); - sdkService = mock(); + sdkService = new MockSdkService(); apiService = mock(); configService = mock(); encryptService = mock(); From 4bf1a3b670961eedbcf6f86dba2ac9e280ace708 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 07:16:46 -0700 Subject: [PATCH 40/41] [deps] Platform: Update Rust crate bytes to v1.10.1 (#14922) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 34819a3981b..28d64e4f504 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -498,9 +498,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "camino" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index c4a2ed98e70..1fce5a7c597 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -18,7 +18,7 @@ base64 = "=0.22.1" bindgen = "=0.71.1" bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "3d48f140fd506412d186203238993163a8c4e536" } byteorder = "=1.5.0" -bytes = "=1.9.0" +bytes = "=1.10.1" cbc = "=0.1.2" core-foundation = "=0.10.0" dirs = "=6.0.0" From 169fdd5883352acae4fcc976d4122da5e9ea0a29 Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Wed, 28 May 2025 10:56:44 -0400 Subject: [PATCH 41/41] [PM-20650] Feature flag addition to clients (#14824) * Feature flag addition to clients * Updating feature flag name --- libs/common/src/enums/feature-flag.enum.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 8cd44f9d627..43b36c5692f 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -59,6 +59,7 @@ export enum FeatureFlag { CipherKeyEncryption = "cipher-key-encryption", PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms", EndUserNotifications = "pm-10609-end-user-notifications", + RemoveCardItemTypePolicy = "pm-16442-remove-card-item-type-policy", /* Platform */ IpcChannelFramework = "ipc-channel-framework", @@ -107,6 +108,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE, [FeatureFlag.EndUserNotifications]: FALSE, [FeatureFlag.PM19941MigrateCipherDomainToSdk]: FALSE, + [FeatureFlag.RemoveCardItemTypePolicy]: FALSE, /* Auth */ [FeatureFlag.PM16117_ChangeExistingPasswordRefactor]: FALSE,