{"results":{"result":{"added-files":{"code-health":0.0,"old-code-health":0.0,"files":[]},"external-review-url":"https://github.com/bitwarden/clients/pull/7445","old-code-health":8.716577259545872,"modified-files":{"code-health":8.691880351560897,"old-code-health":8.716577259545872,"files":[{"file":"libs/common/src/enums/feature-flag.enum.ts","loc":12,"old-loc":12,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts","loc":105,"old-loc":117,"code-health":9.6882083290695,"old-code-health":9.6882083290695},{"file":"apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts","loc":83,"old-loc":89,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts","loc":239,"old-loc":241,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts","loc":511,"old-loc":513,"code-health":8.95348825078179,"old-code-health":8.95348825078179},{"file":"apps/web/src/app/admin-console/organizations/members/people.component.ts","loc":612,"old-loc":622,"code-health":8.526780179894821,"old-code-health":8.526780179894821},{"file":"apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts","loc":81,"old-loc":79,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/tools/vault-export/export.component.ts","loc":114,"old-loc":120,"code-health":9.6882083290695,"old-code-health":10.0},{"file":"apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts","loc":333,"old-loc":334,"code-health":9.494953173000619,"old-code-health":9.494953173000619},{"file":"apps/web/src/app/vault/components/vault-items/vault-items.component.ts","loc":150,"old-loc":159,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/vault/core/views/collection-admin.view.ts","loc":32,"old-loc":32,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts","loc":142,"old-loc":151,"code-health":9.842730062691357,"old-code-health":9.842730062691357},{"file":"apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts","loc":110,"old-loc":121,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/vault/individual-vault/vault.component.ts","loc":898,"old-loc":902,"code-health":7.160342213690138,"old-code-health":7.160342213690138},{"file":"apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts","loc":114,"old-loc":118,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts","loc":130,"old-loc":139,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/vault/org-vault/vault.component.ts","loc":879,"old-loc":883,"code-health":6.906804493033047,"old-code-health":6.906804493033047},{"file":"libs/common/src/vault/models/view/collection.view.ts","loc":48,"old-loc":48,"code-health":9.340036998925854,"old-code-health":9.3931346077612},{"file":"libs/common/src/admin-console/models/domain/organization.ts","loc":228,"old-loc":225,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts","loc":76,"old-loc":89,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/admin-console/organizations/core/services/group/responses/group.response.ts","loc":27,"old-loc":27,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/admin-console/organizations/core/views/group.view.ts","loc":19,"old-loc":19,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts","loc":21,"old-loc":21,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/admin-console/organizations/core/views/organization-user.view.ts","loc":36,"old-loc":36,"code-health":10.0,"old-code-health":10.0},{"file":"libs/common/src/admin-console/abstractions/organization-user/responses/organization-user.response.ts","loc":77,"old-loc":77,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts","loc":64,"old-loc":71,"code-health":10.0,"old-code-health":10.0},{"file":"apps/web/src/app/admin-console/organizations/settings/account.component.ts","loc":205,"old-loc":209,"code-health":9.387218218812514,"old-code-health":9.387218218812514},{"file":"apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts","loc":98,"old-loc":103,"code-health":10.0,"old-code-health":10.0}]},"removed-files":{"code-health":0.0,"old-code-health":0.0,"files":[]},"external-review-id":"7445","analysis-time":"2024-01-16T23:44:55Z","negative-impact-count":3,"suppressions":{"number-of-types":0,"number-of-files-touched":0,"findings":[]},"affected-hotspots":3,"commits":["5a222764ef90475c68fa546aa0dbfae8c0ee78d9","da1fe6d88792b6ba7b11e837ac5e77a3eb8f7524","63acfb266160829c272a649582aba1f264abc176","c791415fa42d8c93d4c5a90604ea71614649be3a","f67d97f465fe39513d1614022e922b94a8ab1523","743aaefb8516b5a14f4609c3c2b1f7d661132e1b","c9ab678a8726ba7faf858bbf1d0276922e4f2578","c0cd6c60320a7a52971b74780d35da53c8b9cddc","6f0df1eac3477a74c69acbf10557861e1f0f5223","8c11fc17f1b7eaf8d8ca72ad621f1cdaa43bf1d9","edb8fd2c83dfce7da48f37196501a0afdee7b308","49efac8702ef2ddead235769a1274b2f4bd2516d","f9cc794e21b5a5bb2b74ee09cafd4d3d9f64e08f","70c13a3757fe081530f24ea26ec8428478b05196","72bb3ed88d9a5ace81bc2e4bb4de9490031623af"],"is-negative-review":false,"negative-findings":{"number-of-types":2,"number-of-files-touched":2,"findings":[{"method":"ExportComponent.constructor","why-it-occurs":"Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.\n\nThe threshold for the TypeScript language is 4 function arguments.","name":"Excess Number of Function Arguments","file":"apps/web/src/app/tools/vault-export/export.component.ts","refactoring-examples":[{"architectural-component-id":null,"author-name":"Alex","training-data":{"loc-added":"4","loc-deleted":"3","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"55413326+AlexRubik@users.noreply.github.com","commit-full-message":"* fix(access-intelligence): split getAtRiskPasswordCountForMember to fix critical drawer counts\n\nSplit the ambiguous optional-arg method into three intent-revealing methods:\n- getAtRiskPasswordCountForMember(memberId): across all apps\n- getAtRiskPasswordCountForMemberInApplication(memberId, applicationName): one app\n- getCriticalAtRiskPasswordCountForMember(memberId): critical apps only\n\nThe critical drawer now calls the critical-scoped helper, so per-member counts\nno longer include apps outside the critical subset. mapMembersToDrawerData\nroutes to the right split method based on whether an app is supplied.\n\nRefs PR #19600 comments: access-intelligence-page.component.ts:371 (CRITICAL),\naccess-report.view.ts:171.\n\n[PM-35052]\n\n* fix(access-intelligence): no-op mark-critical and mark-reviewed for unknown apps\n\nmarkApplicationsAsCritical and markApplicationAsReviewed previously created a\nnew AccessReportSettingsView entry for any app name passed in, even if that app\nwas not in the current report. That allowed stale or incorrect names to persist\nas ghost settings. Both methods now skip unknown names silently so settings can\nonly mutate for apps the report actually contains.\n\nRefs PR #19600 comments: access-report.view.ts:216, access-report.view.ts:258.\n\n[PM-35052]\n\n* fix(access-intelligence): null-guard cipherId in legacy completed-task filter\n\nMirrors the null guard already present in DefaultAccessSecurityTasksService so\nthe legacy implementation does not add null cipherIds to the completed-task\nSet. Pending-task filter is intentionally unchanged; changing it could suppress\nduplicate email notifications Product relies on and needs explicit confirmation\nfirst.\n\nRefs PR #19717 comment: legacy-access-security-tasks.service.ts:57.\n\n[PM-35052]\n\n* refactor(access-intelligence): pipe activeAccount$ directly in persistence service\n\nReplace from(firstValueFrom(getUserId(...))) with direct pipe + take(1) in\nsaveReport$, saveApplicationMetadata$, and loadReport$. The roundtrip via\nPromise was an Observable->Promise->Observable anti-pattern that added\noverhead without any reactive benefit.\n\nRefs PR #19633 comment: default-report-persistence.service.ts:39.\n\n[PM-35052]\n\n* refactor(access-intelligence): use CipherViewLikeUtils.uri for icon URI\n\nReplace custom firstCipher.login?.uris?.[0]?.uri ?? applicationName with the\nshared CipherViewLikeUtils.uri helper so icon URI extraction stays consistent\nwith the rest of the Vault codebase.\n\nRefs PR #19600 comment: default-report-generation.service.ts:168.\n\n[PM-35052]\n\n* refactor(access-intelligence): standardize nullish coalescing in API models\n\nAdd ?? <default> fallbacks to every getResponseProperty call in\nAccessReportMetricsApi (no fallbacks previously) and AccessReportSettingsApi\n(mixed pattern) so the three API models follow a consistent convention.\nAccessReportSummaryApi already uses ?? \"\" and needs no changes.\n\nRefs PR #19612 comment: access-report-summary.api.ts:30.\n\n[PM-35052]\n\n* test(access-intelligence): add spec for DefaultAccessSecurityTasksService\n\nCovers unassignedCriticalCipherIds$ branch behavior (empty report, no tasks,\npending/completed task filtering, null cipherId guard, revision-date vs\nreport-creation-date logic) and\nrequestPasswordChangeForCriticalApplications$ (distinct cipher IDs passed to\nbulk-create, follow-up loadTasks$ call, error propagation).\n\nRefs PR #19717 comment: default-access-security-tasks.service.ts:18.\n\n[PM-35052]\n\n* refactor(access-intelligence): suffix Observable-returning methods with $\n\nRename mapCiphersToMembers, buildMemberRegistry, generateReport,\nAccessReport.decrypt, and AccessReport.fromView to use the $ suffix\nconvention for Observable-returning methods. Abstractions,\nimplementations, call sites, and spec files updated together so no\ncaller lags the rename.\n\nRefs PR #19600 comments: member-cipher-mapping.service.ts:136, :163,\naccess-report.ts:55, access-report.ts:121, report-generation.service.ts:49.\nRefs PR #19612 comments: access-report.ts:55, access-report.ts:121.\n\n[PM-35052]\n\n* chore(access-intelligence): remove commented-out property-level encryption blocks\n\nDelete the stale TODO comments and commented-out constructor branches in\naccess-report.api.ts and access-report.data.ts. Field-level encryption work is\ntracked in PM-32865 and does not need placeholder comments in the code.\n\nRefs PR #19600 comments: access-report.api.ts:56, access-report.data.ts:23, :47.\nRefs PR #19612 comments: access-report.api.ts:43, access-report.data.ts:23, :47.\n\n[PM-35052]\n\n* refactor(access-intelligence): convert allAppsSelected to computed signal\n\nApplicationsTableV2Component.allAppsSelected was a method evaluated three times\nper change detection cycle. Converting to readonly computed memoizes on the\nsignal inputs (dataSource, selectedUrls) without any template change.\n\nRefs PR #19730 comment: applications-table-v2.component.ts:69.\n\n[PM-35052]\n\n* docs(access-intelligence): add class-level JSDoc to metrics and report models\n\nBrief class-level JSDoc on AccessReportMetricsData, AccessReportMetrics,\nAccessReportMetricsView, and AccessReport explains each model's role and how\nit sits in the 4-layer architecture. Method-level JSDoc is unchanged.\n\nRefs PR #19612 comments: access-report-metrics.data.ts:3,\naccess-report-metrics.ts:5, access-report-metrics.view.ts:7, access-report.ts:22.\n\n[PM-35052]\n\n* refactor(access-intelligence): use optional property shorthand for reviewedDate\n\nReplace `reviewedDate: string | undefined` with `reviewedDate?: string` in\nAccessReportSettingsData. Functionally equivalent; the ? shorthand is the\nTypeScript idiom for optional properties.\n\nRefs PR #19612 comment: access-report-settings.data.ts:18.\n\n[PM-35052]\n\n* refactor(access-intelligence): rename loadReport$ to loadLastReport$\n\nThe method returns the most recent persisted report, so loadLastReport$ is\nmore accurate. Updates the abstraction, default implementation, the call\nsites in DefaultAccessIntelligenceDataService, and all spec references. The\nstale TODO comment documenting the rename is removed.\n\nRefs PR #19600 comment: default-report-persistence.service.ts:141.\n\n[PM-35052]\n\n* prettier fixes\n\n* refactor(access-intelligence): move per-member at-risk count onto ApplicationHealthView\n\nPer-application at-risk password counts already live on ApplicationHealthView\n(memberRefs, cipherRefs); compute them there. AccessReportView's wrapper\ngetAtRiskPasswordCountForMemberInApplication is no longer needed: the only\ncaller (mapMembersToDrawerData in access-intelligence-page.component.ts)\nalready has the ApplicationHealthView in scope and now calls\napp.getAtRiskPasswordCountForMember(memberId) directly. The redundant\nname-lookup the wrapper performed internally goes away.\n\nSpec coverage moves from access-report.view.spec.ts to\napplication-health.view.spec.ts (new describe block, follows the local\ncreateReportWithCounts helper convention used by the surrounding tests).\n\nAddresses review feedback:\n- @Banrion (PR #20271, discussion r3184257396): \"this function would best\n  live in the ApplicationHealthView model rather than in the AccessReportView\"\n\n[PM-35052]\n\n* prefer optional properties\n\n* docs(access-intelligence): tighten class-level JSDoc on metrics and report models\n\nReplace verbose class-level JSDoc with focused one-sentence descriptions\nthat answer \"what is this class\" without straying into encryption flow,\nstorage semantics, or implementation details. Removes inaccurate\n\"persisted\" wording (the data layer is client-side serializable form,\nnot a database record).\n\nAddresses review feedback:\n- @Banrion (PR #20271 review 4223206034): \"keep class comments focused on\n  what the class represents and its role in the layer hierarchy\"\n- @Banrion (PR #20271 discussion r3184411809): tighten AccessReportMetrics\n  class summary\n\n[PM-35052]\n\n* docs(access-intelligence): fix stale RiskInsights.fromView$ comments in persistence spec\n\nThe class was renamed RiskInsights to AccessReport in the refactor but two\ncomments in default-report-persistence.service.spec.ts still referenced the\nold name. The actual mocked code (jest.spyOn(AccessReport, \"fromView$\")) is\ncorrect; only the comments were out of date.\n\nAddresses review feedback:\n- @Banrion (PR #20271, discussion r3190138036)\n\n[PM-35052]\n\n* refactor(access-intelligence): use optional chaining for at-risk count selection\n\nReplace the ternary on app with optional chaining + nullish coalescing in\nmapMembersToDrawerData. Functionally equivalent (getAtRiskPasswordCountForMember\nalways returns a number, so ?? only fires when app itself is undefined) and\nmore idiomatic TypeScript.\n\nAddresses review feedback:\n- @Banrion (PR #20271, discussion r3190101083)\n\n[PM-35052]","commit-date":"2026-05-06T15:01:47Z","current-rev":"ae46131469","filename":"clients/bitwarden_license/bit-common/src/dirt/access-intelligence/services/implementations/domain/default-report-generation.service.ts","previous-rev":"4a17483a15","commit-title":"[PM-35052] Address Access Intelligence refactor feedback (#20271)","language":"TypeScript","id":"d0312ddeaeb952dce2bc2556fcbdfe4c2d306c2d","model-score":0.96,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/bitwarden_license/bit-common/src/dirt/access-intelligence/services/implementations/domain/default-report-generation.service.ts b/bitwarden_license/bit-common/src/dirt/access-intelligence/services/implementations/domain/default-report-generation.service.ts\nindex eb45e90e50..2c899dd922 100644\n--- a/bitwarden_license/bit-common/src/dirt/access-intelligence/services/implementations/domain/default-report-generation.service.ts\n+++ b/bitwarden_license/bit-common/src/dirt/access-intelligence/services/implementations/domain/default-report-generation.service.ts\n@@ -3,2 +3,3 @@ import { forkJoin, map, Observable } from \"rxjs\";\n import { CipherView } from \"@bitwarden/common/vault/models/view/cipher.view\";\n+import { CipherViewLikeUtils } from \"@bitwarden/common/vault/utils/cipher-view-like-utils\";\n import { LogService } from \"@bitwarden/logging\";\n@@ -36,3 +37,3 @@ export class DefaultReportGenerationService extends ReportGenerationService {\n \n-  generateReport(\n+  generateReport$(\n     ciphers: CipherView[],\n@@ -95,3 +96,3 @@ export class DefaultReportGenerationService extends ReportGenerationService {\n       healthMap: this.cipherHealthService.checkCipherHealth(ciphers),\n-      mappingResult: this.memberCipherMappingService.mapCiphersToMembers(\n+      mappingResult: this.memberCipherMappingService.mapCiphersToMembers$(\n         ciphers,\n@@ -167,3 +168,3 @@ export class DefaultReportGenerationService extends ReportGenerationService {\n         report.iconCipherId = firstCipher.id;\n-        report.iconUri = firstCipher.login?.uris?.[0]?.uri ?? applicationName;\n+        report.iconUri = CipherViewLikeUtils.uri(firstCipher) ?? applicationName;\n       }\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"KiruthigaManivannan","training-data":{"loc-added":"12","loc-deleted":"1","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"162679756+KiruthigaManivannan@users.noreply.github.com","commit-full-message":"* PM-8545 Defect Upload License Dialog should close after file upload\r\n\r\n* Resolve the validation message issue (#10033)\r\n\r\n---------\r\n\r\nCo-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>","commit-date":"2024-07-10T14:40:18Z","current-rev":"f446c3b454","filename":"clients/apps/web/src/app/billing/shared/update-license-dialog.component.ts","previous-rev":"3d5c0a9b97","commit-title":"PM-8545 Defect Upload License Dialog should close after file upload (#9871)","language":"TypeScript","id":"0463a61c507f3509658ad8b5a773287f76e091a2","model-score":0.73,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.ts b/apps/web/src/app/billing/shared/update-license-dialog.component.ts\nindex 5f9a1e94be..3a690378b8 100644\n--- a/apps/web/src/app/billing/shared/update-license-dialog.component.ts\n+++ b/apps/web/src/app/billing/shared/update-license-dialog.component.ts\n@@ -1 +1,2 @@\n+import { DialogRef } from \"@angular/cdk/dialog\";\n import { Component } from \"@angular/core\";\n@@ -20,2 +21,3 @@ export class UpdateLicenseDialogComponent extends UpdateLicenseComponent {\n   constructor(\n+    private dialogRef: DialogRef,\n     apiService: ApiService,\n@@ -29,4 +31,8 @@ export class UpdateLicenseDialogComponent extends UpdateLicenseComponent {\n   async submitLicense() {\n-    await this.submit();\n+    const result = await this.submit();\n+    if (result === UpdateLicenseDialogResult.Updated) {\n+      this.dialogRef.close(UpdateLicenseDialogResult.Updated);\n+    }\n   }\n+\n   submitLicenseDialog = async () => {\n@@ -34,2 +40,7 @@ export class UpdateLicenseDialogComponent extends UpdateLicenseComponent {\n   };\n+\n+  cancel = async () => {\n+    await this.cancel();\n+    this.dialogRef.close(UpdateLicenseDialogResult.Cancelled);\n+  };\n   static open(dialogService: DialogService) {\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"SmithThe4th","training-data":{"loc-added":"24","loc-deleted":"6","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"gsmith@bitwarden.com","commit-full-message":"* refactored injector of services on the browser service module\r\n\r\n* refactored the search and popup serach service to use state provider\r\n\r\n* renamed back to default\r\n\r\n* removed token service that was readded during merge conflict\r\n\r\n* Updated search service construction on the cli\r\n\r\n* updated to use user key definition\r\n\r\n* Reafctored all components that refernce issearchable\r\n\r\n* removed commented variable\r\n\r\n* added uncommited code to remove dependencies not needed anymore\r\n\r\n* added uncommited code to remove dependencies not needed anymore","commit-date":"2024-04-10T13:02:46Z","current-rev":"2bce6c538c","filename":"clients/libs/angular/src/vault/components/vault-items.component.ts","previous-rev":"560033cb88","commit-title":"[PM-6194] Refactor injection of services in browser services module (#8380)","language":"TypeScript","id":"611e457c24be2d4ae3437b3547dcf1c70d1b4a50","model-score":0.69,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/libs/angular/src/vault/components/vault-items.component.ts b/libs/angular/src/vault/components/vault-items.component.ts\nindex cdfb1b6299..458b10865c 100644\n--- a/libs/angular/src/vault/components/vault-items.component.ts\n+++ b/libs/angular/src/vault/components/vault-items.component.ts\n@@ -1,2 +1,3 @@\n-import { Directive, EventEmitter, Input, Output } from \"@angular/core\";\n+import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from \"@angular/core\";\n+import { BehaviorSubject, Subject, from, switchMap, takeUntil } from \"rxjs\";\n \n@@ -8,3 +9,3 @@ import { CipherView } from \"@bitwarden/common/vault/models/view/cipher.view\";\n @Directive()\n-export class VaultItemsComponent {\n+export class VaultItemsComponent implements OnInit, OnDestroy {\n   @Input() activeCipherId: string = null;\n@@ -25,9 +26,11 @@ export class VaultItemsComponent {\n \n+  private destroy$ = new Subject<void>();\n   private searchTimeout: any = null;\n-  private _searchText: string = null;\n+  private isSearchable: boolean = false;\n+  private _searchText$ = new BehaviorSubject<string>(\"\");\n   get searchText() {\n-    return this._searchText;\n+    return this._searchText$.value;\n   }\n   set searchText(value: string) {\n-    this._searchText = value;\n+    this._searchText$.next(value);\n   }\n@@ -39,2 +42,17 @@ export class VaultItemsComponent {\n \n+  ngOnInit(): void {\n+    this._searchText$\n+      .pipe(\n+        switchMap((searchText) => from(this.searchService.isSearchable(searchText))),\n+        takeUntil(this.destroy$),\n+      )\n+      .subscribe((isSearchable) => {\n+        this.isSearchable = isSearchable;\n+      });\n+  }\n+\n+  ngOnDestroy(): void {\n+    throw new Error(\"Method not implemented.\");\n+  }\n+\n   async load(filter: (cipher: CipherView) => boolean = null, deleted = false) {\n@@ -92,3 +110,3 @@ export class VaultItemsComponent {\n   isSearching() {\n-    return !this.searchPending && this.searchService.isSearchable(this.searchText);\n+    return !this.searchPending && this.isSearchable;\n   }\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"Jordan Aasen","training-data":{"loc-added":"24","loc-deleted":"1","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"166539328+jaasen-livefront@users.noreply.github.com","commit-full-message":"* hide restricted cipher types in file menu on desktop\n\n* fix bitwarden menu\n\n* small fixes","commit-date":"2025-07-23T16:33:45Z","current-rev":"2040be68e3","filename":"clients/apps/desktop/src/main/menu/menu.file.ts","previous-rev":"417c4cd13b","commit-title":"[PM-23360] - Hide restricted cipher types in \"File -> New Item\" on desktop (#15743)","language":"TypeScript","id":"8d88e6b987aae0c6a36bc970d232ce6f56c68815","model-score":0.69,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/desktop/src/main/menu/menu.file.ts b/apps/desktop/src/main/menu/menu.file.ts\nindex 19ba5e9979..a8cdb347a7 100644\n--- a/apps/desktop/src/main/menu/menu.file.ts\n+++ b/apps/desktop/src/main/menu/menu.file.ts\n@@ -4,2 +4,3 @@ import { I18nService } from \"@bitwarden/common/platform/abstractions/i18n.servic\n import { MessagingService } from \"@bitwarden/common/platform/abstractions/messaging.service\";\n+import { CipherType } from \"@bitwarden/sdk-internal\";\n \n@@ -56,2 +57,3 @@ export class FileMenu extends FirstMenu implements IMenubarMenu {\n     isLockable: boolean,\n+    private restrictedCipherTypes: CipherType[],\n   ) {\n@@ -79,2 +81,19 @@ export class FileMenu extends FirstMenu implements IMenubarMenu {\n \n+  private mapMenuItemToCipherType(itemId: string): CipherType {\n+    switch (itemId) {\n+      case \"typeLogin\":\n+        return CipherType.Login;\n+      case \"typeCard\":\n+        return CipherType.Card;\n+      case \"typeIdentity\":\n+        return CipherType.Identity;\n+      case \"typeSecureNote\":\n+        return CipherType.SecureNote;\n+      case \"typeSshKey\":\n+        return CipherType.SshKey;\n+      default:\n+        throw new Error(`Unknown menu item id: ${itemId}`);\n+    }\n+  }\n+\n   private get addNewItemSubmenu(): MenuItemConstructorOptions[] {\n@@ -111,3 +130,7 @@ export class FileMenu extends FirstMenu implements IMenubarMenu {\n       },\n-    ];\n+    ].filter((item) => {\n+      return !this.restrictedCipherTypes?.some(\n+        (restrictedType) => restrictedType === this.mapMenuItemToCipherType(item.id),\n+      );\n+    });\n   }\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"Stephon Brown","training-data":{"loc-added":"23","loc-deleted":"13","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"sbrown@livefront.com","commit-full-message":"* refactor(billing): Modernize organization plans template with block syntax\n\n* Refactor: Update `@for` loop tracking with `$index`\n\n* Feat: Migrate component properties to Angular signals\n\n* Refactor: Adapt component logic for signal consumption and improve structure\n\n* feat(admin-console): Bind OrganizationPlansComponent inputs directly in template\n\n* refactor(admin-console): Remove ViewChild for OrganizationPlansComponent configuration\n\n* refactor(billing): Switch productTier and plan to input signals\n\n* refactor(billing): Update productTier and plan access to use form controls\n\n* refactor(billing): Rename 'isCreatingNewOrganization' getter\n\n* refactor(billing): Access enterPaymentMethodComponent as a signal\n\n* feat(billing): use createOrganization property for organization creation flow\n\n* refactor(billing): migrate organization plans tests to signals and reactive forms\n\n* refactor(billing): introduce setupMockUpgradeOrganization helper in tests\n\n* refactor(billing): update getters to computed signals\n\n* refactor(billing): update template to use new computed signals\n\n* tests(billing): update tests with computed signals\n\n* feat(billing): update display of specific forms when user is upgrading from premium\n\n* tests(billing): add premium upgrade tests\n\n* fix(billing): update type issues for organization plans\n\n* tests(billing): update failing test\n\n* refactor(billing): rename plan/productTier inputs to initial* for clarity\n\n* refactor(billing): update templates to use renamed initial* inputs\n\n* test(billing): update tests to use renamed initial* inputs\n\n* feat(billing): feature flag upgrade from premium changes\n\n* test(billing): update tests for featureflag\n\n* refactor(ui): Improve HTML templating for organization plans\n\n* refactor(component): Safely derive 'hasPremiumPersonally' signal\n\n* refactor(component): Streamline 'createCloudHosted' method and key handling\n\n* feat(billing): Enable organization key submission for premium upgrades\n\n* test(billing): Enhance and adjust organization plan submission tests\n\n* refactor: remove unused imports and types\n\n* refactor(billing): introduce DTO for organization upgrade request\n\n* feat(billing): implement organization key and collection encryption\n\n* refactor(billing): integrate DTO and encryption logic in upgrade service\n\n* test(billing): update specs and components for organization upgrade DTO\n\n* refactor(billing): relocate FamiliesForEnterpriseSetupComponent to billing module\n\n* refactor(admin-console): revert families-for-enterprise component\n\n* refactor: rename wrappedPrivateKey to encryptedPrivateKey\n\n* refactor(billing): rename encrypted private key variable\n\n* feat(billing): add tier ids and types\n\n* refactor(billing): remove unused imports and sync service\n\n* fix(billing): correct tier passing in upgrade function\n\n* feat(billing): add conversion function from product tier to subscription tier id\n\n* refactor(billing): enhance organization encryption data generation\n\n* fix(billing): enable response for account upgrade\n\n* fix(billing): pass plan tier instead of plan details\n\n* refactor(test): remove unnecessary organization service mock\n\n* test: update upgrade service tests\n\n* tests(billing): update tests\n\n* refactor(billing): import account type\nfeat(billing): add organization creation message\n\n* refactor(billing): remove unused imports and constants\n\n* refactor(billing): remove unused viewchild\n\n* fix(billing): remove selectedfile variable\n\n* feat(billing): update organization upgrade logic\n\n* refactor(billing): remove unused upgrade functions\n\n* test: remove unnecessary setupMockEncryptionKeys calls\n\n* refactor: remove unused mockAccountBillingClient\n\n* feat: introduce PremiumOrgUpgradeService\n\n* refactor: simplify form value access and remove accountbilling\n\n* fix(billing): handle type validation\n\n* test(billing): add initial plan and product tier input tests\n\n* feat(billing): set initial plan and product tier values\n\n* refactor(billing): ensure selected plan exists before accessing type","commit-date":"2026-03-02T17:16:29Z","current-rev":"2facb7e04f","filename":"clients/apps/web/src/app/billing/clients/account-billing.client.ts","previous-rev":"52c551c0fe","commit-title":"[PM-29823] New Organization Upgrade Path (#19080)","language":"TypeScript","id":"8bc8a8c8329aaf52a21adeef6cea7a59dec446b9","model-score":0.56,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/web/src/app/billing/clients/account-billing.client.ts b/apps/web/src/app/billing/clients/account-billing.client.ts\nindex 6864e1de98..3b0d3a3910 100644\n--- a/apps/web/src/app/billing/clients/account-billing.client.ts\n+++ b/apps/web/src/app/billing/clients/account-billing.client.ts\n@@ -16,2 +16,13 @@ import {\n \n+export type UpgradePremiumToOrganizationRequest = {\n+  organizationName: string;\n+  organizationKey: string;\n+  collectionName: string | null;\n+  publicKey: string;\n+  encryptedPrivateKey: string;\n+  planTier: ProductTierType;\n+  cadence: SubscriptionCadence;\n+  billingAddress: Pick<BillingAddress, \"country\" | \"postalCode\">;\n+};\n+\n @Injectable()\n@@ -68,10 +79,6 @@ export class AccountBillingClient {\n   upgradePremiumToOrganization = async (\n-    organizationName: string,\n-    organizationKey: string,\n-    planTier: ProductTierType,\n-    cadence: SubscriptionCadence,\n-    billingAddress: Pick<BillingAddress, \"country\" | \"postalCode\">,\n-  ): Promise<void> => {\n+    request: UpgradePremiumToOrganizationRequest,\n+  ): Promise<string> => {\n     const path = `${this.endpoint}/upgrade`;\n-    await this.apiService.send(\n+    return await this.apiService.send(\n       \"POST\",\n@@ -79,10 +86,13 @@ export class AccountBillingClient {\n       {\n-        organizationName,\n-        key: organizationKey,\n-        targetProductTierType: planTier,\n-        cadence,\n-        billingAddress,\n+        organizationName: request.organizationName,\n+        key: request.organizationKey,\n+        collectionName: request.collectionName,\n+        publicKey: request.publicKey,\n+        encryptedPrivateKey: request.encryptedPrivateKey,\n+        targetProductTierType: request.planTier,\n+        cadence: request.cadence as string,\n+        billingAddress: request.billingAddress,\n       },\n       true,\n-      false,\n+      true,\n     );\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"Andreas Coroiu","training-data":{"loc-added":"6","loc-deleted":"7","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.387218218812514"},"author-email":"acoroiu@bitwarden.com","commit-full-message":"* feat: block window creation from main web renderer\n\n* feat: redirect safe urls to external browser\n\n* [PM-32235] Block unsafe open external call with arbitrary web vault url argument (#19322)\n\n* feat: add safe shell class with openExternal impl\n\n* feat: add lint rule to avoid directy openExternal usage\n\n* feat: replace all unsafe usages\n\n* feat: allow multiple allow-lists\n\n* feat: update usages\n\n* chore: remove static version of function\n\n* fix: additional usages\n\n* fix: tests\n\n* fix: construct arguments\n\n* fix: lint\n\n* feat: block insecure weburls","commit-date":"2026-03-17T14:42:20Z","current-rev":"300b920457","filename":"clients/apps/desktop/src/main/menu/menu.account.ts","previous-rev":"ebbd9ed0ba","commit-title":"[PM-32248] [PM-32233] [PM-32235] Block links from opening in electron window (#19317)","language":"TypeScript","id":"c5dd9865fdfa6dd4c241becbb68f90f5ff2c0ebd","model-score":0.54,"author-id":null,"project-id":26215,"delta-file-score":0.29056275,"diff":"diff --git a/apps/desktop/src/main/menu/menu.account.ts b/apps/desktop/src/main/menu/menu.account.ts\nindex f3c9b08531..93e19dbbfe 100644\n--- a/apps/desktop/src/main/menu/menu.account.ts\n+++ b/apps/desktop/src/main/menu/menu.account.ts\n@@ -1,2 +1,2 @@\n-import { BrowserWindow, dialog, MenuItemConstructorOptions, shell } from \"electron\";\n+import { BrowserWindow, dialog, MenuItemConstructorOptions } from \"electron\";\n \n@@ -4,3 +4,5 @@ import { I18nService } from \"@bitwarden/common/platform/abstractions/i18n.servic\n import { MessagingService } from \"@bitwarden/common/platform/abstractions/messaging.service\";\n+import { UrlType } from \"@bitwarden/common/platform/misc/safe-urls\";\n \n+import { SafeShell } from \"../../platform/main/safe-shell.main\";\n import { isMacAppStore, isWindowsStore } from \"../../utils\";\n@@ -42,2 +44,3 @@ export class AccountMenu implements IMenubarMenu {\n     hasMasterPassword: boolean,\n+    private shell: SafeShell,\n   ) {\n@@ -76,5 +79,3 @@ export class AccountMenu implements IMenubarMenu {\n         if (result.response === 0) {\n-          // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.\n-          // eslint-disable-next-line @typescript-eslint/no-floating-promises\n-          shell.openExternal(this._webVaultUrl);\n+          void this.shell.openExternal(this._webVaultUrl, UrlType.WebUrl);\n         }\n@@ -100,5 +101,3 @@ export class AccountMenu implements IMenubarMenu {\n         if (result.response === 0) {\n-          // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.\n-          // eslint-disable-next-line @typescript-eslint/no-floating-promises\n-          shell.openExternal(this._webVaultUrl);\n+          void this.shell.openExternal(this._webVaultUrl, UrlType.WebUrl);\n         }\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"Jake Fink","training-data":{"loc-added":"2","loc-deleted":"47","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"jfink@bitwarden.com","commit-full-message":"","commit-date":"2024-05-21T13:34:03Z","current-rev":"56c4be4f1a","filename":"clients/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts","previous-rev":"ead52698c3","commit-title":"consolidate login strategy deps (#8859)","language":"TypeScript","id":"775a450c833a249ad84364ec43d7b36d3f046417","model-score":0.46,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts\nindex 226ab1799a..d283d163da 100644\n--- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts\n+++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.ts\n@@ -3,9 +3,2 @@ import { Jsonify } from \"type-fest\";\n \n-import { ApiService } from \"@bitwarden/common/abstractions/api.service\";\n-import { VaultTimeoutSettingsService } from \"@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service\";\n-import { AccountService } from \"@bitwarden/common/auth/abstractions/account.service\";\n-import { KdfConfigService } from \"@bitwarden/common/auth/abstractions/kdf-config.service\";\n-import { InternalMasterPasswordServiceAbstraction } from \"@bitwarden/common/auth/abstractions/master-password.service.abstraction\";\n-import { TokenService } from \"@bitwarden/common/auth/abstractions/token.service\";\n-import { TwoFactorService } from \"@bitwarden/common/auth/abstractions/two-factor.service\";\n import { AuthResult } from \"@bitwarden/common/auth/models/domain/auth-result\";\n@@ -13,9 +6,2 @@ import { WebAuthnLoginTokenRequest } from \"@bitwarden/common/auth/models/request\n import { IdentityTokenResponse } from \"@bitwarden/common/auth/models/response/identity-token.response\";\n-import { BillingAccountProfileStateService } from \"@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service\";\n-import { AppIdService } from \"@bitwarden/common/platform/abstractions/app-id.service\";\n-import { CryptoService } from \"@bitwarden/common/platform/abstractions/crypto.service\";\n-import { LogService } from \"@bitwarden/common/platform/abstractions/log.service\";\n-import { MessagingService } from \"@bitwarden/common/platform/abstractions/messaging.service\";\n-import { PlatformUtilsService } from \"@bitwarden/common/platform/abstractions/platform-utils.service\";\n-import { StateService } from \"@bitwarden/common/platform/abstractions/state.service\";\n import { SymmetricCryptoKey } from \"@bitwarden/common/platform/models/domain/symmetric-crypto-key\";\n@@ -24,3 +10,2 @@ import { UserKey } from \"@bitwarden/common/types/key\";\n \n-import { InternalUserDecryptionOptionsServiceAbstraction } from \"../abstractions\";\n import { WebAuthnLoginCredentials } from \"../models/domain/login-credentials\";\n@@ -48,35 +33,5 @@ export class WebAuthnLoginStrategy extends LoginStrategy {\n     data: WebAuthnLoginStrategyData,\n-    accountService: AccountService,\n-    masterPasswordService: InternalMasterPasswordServiceAbstraction,\n-    cryptoService: CryptoService,\n-    apiService: ApiService,\n-    tokenService: TokenService,\n-    appIdService: AppIdService,\n-    platformUtilsService: PlatformUtilsService,\n-    messagingService: MessagingService,\n-    logService: LogService,\n-    stateService: StateService,\n-    twoFactorService: TwoFactorService,\n-    userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,\n-    billingAccountProfileStateService: BillingAccountProfileStateService,\n-    vaultTimeoutSettingsService: VaultTimeoutSettingsService,\n-    kdfConfigService: KdfConfigService,\n+    ...sharedDeps: ConstructorParameters<typeof LoginStrategy>\n   ) {\n-    super(\n-      accountService,\n-      masterPasswordService,\n-      cryptoService,\n-      apiService,\n-      tokenService,\n-      appIdService,\n-      platformUtilsService,\n-      messagingService,\n-      logService,\n-      stateService,\n-      twoFactorService,\n-      userDecryptionOptionsService,\n-      billingAccountProfileStateService,\n-      vaultTimeoutSettingsService,\n-      kdfConfigService,\n-    );\n+    super(...sharedDeps);\n \n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"Max","training-data":{"loc-added":"4","loc-deleted":"24","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.494953173000619"},"author-email":"mpower@bitwarden.com","commit-full-message":"* added phishing blocker toggle\n\n* design improvements\n\n* Fix TypeScript strict mode errors in PhishingDetectionSettingsServiceAbstraction\n\n* Camel case messages\n\n* Update PhishingDetectionService.initialize parameter ordering\n\n* Add comments to PhishingDetectionSettingsServiceAbstraction\n\n* Change state from global to user settings\n\n* Remove clear on logout phishing-detection-settings\n\n* PM-28536 making a change from getActive to getUser because of method being deprecated\n\n* Moved phishing detection services to own file\n\n* Added new phishing detection availability service to expose complex enable logic\n\n* Add test cases for PhishingDetectionAvailabilityService\n\n* Remove phishing detection availability in favor of one settings service\n\n* Extract phishing detection settings service abstraction to own file\n\n* Update phishing detection-settings service to include availability logic. Updated dependencies\n\n* Add test cases for phishing detection element. Added missing dependencies in testbed setup\n\n* Update services in extension\n\n* Switch checkbox to bit-switch component\n\n* Remove comment\n\n* Remove comment\n\n* Fix prettier vs lint spacing\n\n* Replace deprecated active user state. Updated test cases\n\n* Fix account-security test failing\n\n* Update comments\n\n* Renamed variable\n\n* Removed obsolete message\n\n* Remove unused variable\n\n* Removed unused import\n\n---------\n\nCo-authored-by: Leslie Tilton <23057410+Banrion@users.noreply.github.com>\nCo-authored-by: Graham Walker <gwalker@bitwarden.com>\nCo-authored-by: Tom <144813356+ttalty@users.noreply.github.com>","commit-date":"2025-12-15T15:51:31Z","current-rev":"721f253ef9","filename":"clients/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts","previous-rev":"898c5d366a","commit-title":"[PM-28536] Add phishing blocker setting to account security (#17527)","language":"TypeScript","id":"8aa713f8ee1082f7faa6831f3e006ef878889813","model-score":0.41,"author-id":null,"project-id":26215,"delta-file-score":0.37199178,"diff":"diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts\nindex 4917e740be..e04d08559a 100644\n--- a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts\n+++ b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts\n@@ -1,3 +1,2 @@\n import {\n-  combineLatest,\n   concatMap,\n@@ -8,3 +7,2 @@ import {\n   merge,\n-  of,\n   Subject,\n@@ -14,6 +12,3 @@ import {\n \n-import { AccountService } from \"@bitwarden/common/auth/abstractions/account.service\";\n-import { BillingAccountProfileStateService } from \"@bitwarden/common/billing/abstractions\";\n-import { FeatureFlag } from \"@bitwarden/common/enums/feature-flag.enum\";\n-import { ConfigService } from \"@bitwarden/common/platform/abstractions/config/config.service\";\n+import { PhishingDetectionSettingsServiceAbstraction } from \"@bitwarden/common/dirt/services/abstractions/phishing-detection-settings.service.abstraction\";\n import { LogService } from \"@bitwarden/common/platform/abstractions/log.service\";\n@@ -52,7 +47,5 @@ export class PhishingDetectionService {\n   static initialize(\n-    accountService: AccountService,\n-    billingAccountProfileStateService: BillingAccountProfileStateService,\n-    configService: ConfigService,\n     logService: LogService,\n     phishingDataService: PhishingDataService,\n+    phishingDetectionSettingsService: PhishingDetectionSettingsServiceAbstraction,\n     messageListener: MessageListener,\n@@ -120,18 +113,5 @@ export class PhishingDetectionService {\n \n-    const activeAccountHasAccess$ = combineLatest([\n-      accountService.activeAccount$,\n-      configService.getFeatureFlag$(FeatureFlag.PhishingDetection),\n-    ]).pipe(\n-      switchMap(([account, featureEnabled]) => {\n-        if (!account) {\n-          logService.debug(\"[PhishingDetectionService] No active account.\");\n-          return of(false);\n-        }\n-        return billingAccountProfileStateService\n-          .hasPremiumFromAnySource$(account.id)\n-          .pipe(map((hasPremium) => hasPremium && featureEnabled));\n-      }),\n-    );\n+    const phishingDetectionActive$ = phishingDetectionSettingsService.on$;\n \n-    const initSub = activeAccountHasAccess$\n+    const initSub = phishingDetectionActive$\n       .pipe(\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"Vincent Salucci","training-data":{"loc-added":"9","loc-deleted":"44","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.512342739740653"},"author-email":"26154748+vincentsalucci@users.noreply.github.com","commit-full-message":"* chore: remove fc v1 from org.canEditAnyCollection and update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from collectionView.canEdit and update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from organization.canEditAllCiphers and update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from canDeleteAnyCollection, collection views, update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from canEditUser/GroupAccess, refs PM-10294\r\n\r\n* chore: remove fc v1 from canViewCollectionInfo, refs PM-10294\r\n\r\n* chore: remove fc v1 from account component, refs PM-10294\r\n\r\n* fix: remove fc v1 from collections component, refs PM-10294\r\n\r\n* fix: update vault-items component, refs PM-10294\r\n\r\n* fix: remove fc v1 from collection-dialog and collections components, refs PM-10294\r\n\r\n* chore: remove ConfigService from group-add-edit and account components, refs PM-10294\r\n\r\n* chore: change canEditAnyCollection to getter and update callers, refs PM-10294\r\n\r\n* chore: change canEditUnmanagedCollections to getter and update callers, refs PM-10294\r\n\r\n* chore: change canDeleteAnyCollection to getter and update callers, refs PM-10294\r\n\r\n* chore: remove deprecated observable and update comments with v1, refs PM-10294\r\n\r\n* chore: remove ununsed ConfigService from collection-dialog component, refs PM-10294\r\n\r\n* chore: remove final fc v1 ref for vault-collection-row, refs PM-10294","commit-date":"2024-08-13T15:45:41Z","current-rev":"471dd3bd7b","filename":"clients/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts","previous-rev":"43da67ee51","commit-title":"[PM-10294] Remove FC v1 from Clients (#10422)","language":"TypeScript","id":"335ab91c1e76ba46999fd7939ffc64d695d9e743","model-score":0.25,"author-id":null,"project-id":26215,"delta-file-score":0.2948975,"diff":"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\nindex 81830d1213..ef36f2b80b 100644\n--- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts\n+++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts\n@@ -25,4 +25,2 @@ import { AccountService } from \"@bitwarden/common/auth/abstractions/account.serv\n import { ProductTierType } from \"@bitwarden/common/billing/enums\";\n-import { FeatureFlag } from \"@bitwarden/common/enums/feature-flag.enum\";\n-import { ConfigService } from \"@bitwarden/common/platform/abstractions/config/config.service\";\n import { I18nService } from \"@bitwarden/common/platform/abstractions/i18n.service\";\n@@ -145,3 +143,2 @@ export class MemberDialogComponent implements OnDestroy {\n     private dialogService: DialogService,\n-    private configService: ConfigService,\n     private accountService: AccountService,\n@@ -176,11 +173,4 @@ export class MemberDialogComponent implements OnDestroy {\n \n-    this.allowAdminAccessToAllCollectionItems$ = combineLatest([\n-      this.organization$,\n-      this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),\n-    ]).pipe(\n-      map(([organization, flexibleCollectionsV1Enabled]) => {\n-        if (!flexibleCollectionsV1Enabled) {\n-          return true;\n-        }\n-\n+    this.allowAdminAccessToAllCollectionItems$ = this.organization$.pipe(\n+      map((organization) => {\n         return organization.allowAdminAccessToAllCollectionItems;\n@@ -210,9 +200,4 @@ export class MemberDialogComponent implements OnDestroy {\n \n-    const flexibleCollectionsV1Enabled$ = this.configService.getFeatureFlag$(\n-      FeatureFlag.FlexibleCollectionsV1,\n-    );\n-\n     this.canAssignAccessToAnyCollection$ = combineLatest([\n       this.organization$,\n-      flexibleCollectionsV1Enabled$,\n       this.allowAdminAccessToAllCollectionItems$,\n@@ -220,4 +205,4 @@ export class MemberDialogComponent implements OnDestroy {\n       map(\n-        ([org, flexibleCollectionsV1Enabled, allowAdminAccessToAllCollectionItems]) =>\n-          org.canEditAnyCollection(flexibleCollectionsV1Enabled) ||\n+        ([org, allowAdminAccessToAllCollectionItems]) =>\n+          org.canEditAnyCollection ||\n           // Manage Users custom permission cannot edit any collection but they can assign access from this dialog\n@@ -233,45 +218,35 @@ export class MemberDialogComponent implements OnDestroy {\n       groups: groups$,\n-      flexibleCollectionsV1Enabled: flexibleCollectionsV1Enabled$,\n     })\n       .pipe(takeUntil(this.destroy$))\n-      .subscribe(\n-        ({ organization, collections, userDetails, groups, flexibleCollectionsV1Enabled }) => {\n-          this.setFormValidators(organization);\n+      .subscribe(({ organization, collections, userDetails, groups }) => {\n+        this.setFormValidators(organization);\n \n-          // Groups tab: populate available groups\n-          this.groupAccessItems = [].concat(\n-            groups.map<AccessItemView>((g) => mapGroupToAccessItemView(g)),\n-          );\n+        // Groups tab: populate available groups\n+        this.groupAccessItems = [].concat(\n+          groups.map<AccessItemView>((g) => mapGroupToAccessItemView(g)),\n+        );\n \n-          // Collections tab: Populate all available collections (including current user access where applicable)\n-          this.collectionAccessItems = collections\n-            .map((c) =>\n-              mapCollectionToAccessItemView(\n-                c,\n-                organization,\n-                flexibleCollectionsV1Enabled,\n-                userDetails == null\n-                  ? undefined\n-                  : c.users.find((access) => access.id === userDetails.id),\n-              ),\n-            )\n-            // But remove collections that we can't assign access to, unless the user is already assigned\n-            .filter(\n-              (item) =>\n-                !item.readonly || userDetails?.collections.some((access) => access.id == item.id),\n-            );\n-\n-          if (userDetails != null) {\n-            this.loadOrganizationUser(\n-              userDetails,\n-              groups,\n-              collections,\n+        // Collections tab: Populate all available collections (including current user access where applicable)\n+        this.collectionAccessItems = collections\n+          .map((c) =>\n+            mapCollectionToAccessItemView(\n+              c,\n               organization,\n-              flexibleCollectionsV1Enabled,\n-            );\n-          }\n+              userDetails == null\n+                ? undefined\n+                : c.users.find((access) => access.id === userDetails.id),\n+            ),\n+          )\n+          // But remove collections that we can't assign access to, unless the user is already assigned\n+          .filter(\n+            (item) =>\n+              !item.readonly || userDetails?.collections.some((access) => access.id == item.id),\n+          );\n \n-          this.loading = false;\n-        },\n-      );\n+        if (userDetails != null) {\n+          this.loadOrganizationUser(userDetails, groups, collections, organization);\n+        }\n+\n+        this.loading = false;\n+      });\n   }\n@@ -299,3 +274,2 @@ export class MemberDialogComponent implements OnDestroy {\n     organization: Organization,\n-    flexibleCollectionsV1Enabled: boolean,\n   ) {\n@@ -343,9 +317,3 @@ export class MemberDialogComponent implements OnDestroy {\n       collectionsFromGroups.map(({ collection, accessSelection, group }) =>\n-        mapCollectionToAccessItemView(\n-          collection,\n-          organization,\n-          flexibleCollectionsV1Enabled,\n-          accessSelection,\n-          group,\n-        ),\n+        mapCollectionToAccessItemView(collection, organization, accessSelection, group),\n       ),\n@@ -623,3 +591,2 @@ function mapCollectionToAccessItemView(\n   organization: Organization,\n-  flexibleCollectionsV1Enabled: boolean,\n   accessSelection?: CollectionAccessSelectionView,\n@@ -632,5 +599,3 @@ function mapCollectionToAccessItemView(\n     listName: collection.name,\n-    readonly:\n-      group !== undefined ||\n-      !collection.canEditUserAccess(organization, flexibleCollectionsV1Enabled),\n+    readonly: group !== undefined || !collection.canEditUserAccess(organization),\n     readonlyPermission: accessSelection ? convertToPermission(accessSelection) : undefined,\n","improvement-type":"Excess Number of Function Arguments"}],"change-level":"warning","is-hotspot?":false,"line":31,"what-changed":"ExportComponent.constructor has 11 arguments, threshold = 4","how-to-fix":"Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring [INTRODUCE PARAMETER OBJECT](https://refactoring.com/catalog/introduceParameterObject.html) to encapsulate arguments that refer to the same logical concept.","change-type":"introduced"},{"method":"CollectionView.canDelete","why-it-occurs":"A Complex Method has a high cyclomatic complexity. The recommended threshold for the TypeScript language is a cyclomatic complexity lower than 9.","name":"Complex Method","file":"libs/common/src/vault/models/view/collection.view.ts","refactoring-examples":[{"architectural-component-id":null,"author-name":"Jason Ng","training-data":{"loc-added":"2","loc-deleted":"5","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"jng@bitwarden.com","commit-full-message":"","commit-date":"2026-02-11T14:53:46Z","current-rev":"d18ddd3480","filename":"clients/apps/desktop/src/vault/app/vault/item-footer.component.ts","previous-rev":"4852f8fc4f","commit-title":"[PM-31680] remove archive buttons from footer for edit view desktop (#18858)","language":"TypeScript","id":"2bb04750258319dacf9c39935ef94d8ee4aea73e","model-score":0.96,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.ts b/apps/desktop/src/vault/app/vault/item-footer.component.ts\nindex 02c9873c29..133a9777fa 100644\n--- a/apps/desktop/src/vault/app/vault/item-footer.component.ts\n+++ b/apps/desktop/src/vault/app/vault/item-footer.component.ts\n@@ -265,6 +265,3 @@ export class ItemFooterComponent implements OnInit, OnChanges {\n     this.showArchiveButton =\n-      cipherCanBeArchived &&\n-      userCanArchive &&\n-      (this.action === \"view\" || this.action === \"edit\") &&\n-      !this.cipher.isArchived;\n+      cipherCanBeArchived && userCanArchive && this.action === \"view\" && !this.cipher.isArchived;\n \n@@ -273,3 +270,3 @@ export class ItemFooterComponent implements OnInit, OnChanges {\n       hasArchiveFlagEnabled &&\n-      (this.action === \"view\" || this.action === \"edit\") &&\n+      this.action === \"view\" &&\n       this.cipher.isArchived &&\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nick Krantz","training-data":{"loc-added":"4","loc-deleted":"2","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"125900171+nick-livefront@users.noreply.github.com","commit-full-message":"* remove todo\n\n* Retrieve cache cipher for add-edit form\n\n* user prefilled cipher for add-edit form\n\n* add listener for clearing view cache\n\n* clear local cache when clearing global state\n\n* track initial value of cache for down stream logic that should only occur on non-cached values\n\n* add feature flag for edit form persistence\n\n* add tests for cipher form cache service\n\n* fix optional initialValues\n\n* add services to cipher form storybook\n\n* fix strict types\n\n* rename variables to be platform agnostic\n\n* use deconstructed collectionIds variable to avoid them be overwritten\n\n* use the originalCipherView for initial values\n\n* add comment about signal equality\n\n* prevent events from being emitted when adding uris to the existing form\n\n- This stops other values from being overwrote in the initialization process\n\n* add check for cached cipher when adding initial uris","commit-date":"2025-01-22T16:49:07Z","current-rev":"5c32e5020d","filename":"clients/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts","previous-rev":"1dfae06856","commit-title":"[PM-9111] Extension: persist add/edit form (#12236)","language":"TypeScript","id":"137e2efd304af2a9a4d5c9d5cfddae574adf1def","model-score":0.95,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts\nindex cd75fe8fba..f17432a993 100644\n--- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts\n+++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts\n@@ -150,4 +150,6 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {\n \n-    // Populate the form with the existing fields\n-    this.cipherFormContainer.originalCipherView?.fields?.forEach((field) => {\n+    const prefillCipher = this.cipherFormContainer.getInitialCipherView();\n+\n+    // When available, populate the form with the existing fields\n+    prefillCipher.fields?.forEach((field) => {\n       let value: string | boolean = field.value;\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Jared","training-data":{"loc-added":"2","loc-deleted":"8","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"TheWolfBadger@gmail.com","commit-full-message":"* Refactor: Remove ConfigService dependency from auto-confirm related components\n\n- Eliminated ConfigService from MainBackground, AutoConfirmPolicy, UserLayoutComponent, WebVaultPromptService, and organizationPolicyGuard.\n- Updated logic to directly use organization properties instead of feature flags for auto-confirm functionality.\n- Adjusted tests in DefaultAutomaticUserConfirmationService to reflect the removal of feature flag checks.\n- Cleaned up unused imports related to ConfigService across various files.\n\n* Refactor: Update date handling in tests and remove unused feature flag checks\n\n- Changed date calculation in WebVaultExtensionPromptService tests to use milliseconds for accuracy.\n- Removed unused feature flag checks from WebVaultPromptService tests, simplifying the logic and improving clarity.\n\n* Refactor: Update organizationPolicyGuard to include ConfigService in feature callback\n\n- Modified the organizationPolicyGuard to accept ConfigService as an additional parameter in the feature callback.\n- Adjusted the SendComponent route to align with the updated guard implementation.\n\n* Fix: Adjust date calculation in WebVaultExtensionPromptService tests for accuracy\n\n- Updated the test to set the exact date to 30 days prior using setDate method for clarity and to avoid potential issues with DST boundaries.","commit-date":"2026-04-09T19:36:38Z","current-rev":"79c6b51599","filename":"clients/apps/web/src/app/vault/services/web-vault-prompt.service.ts","previous-rev":"14cd2ad341","commit-title":"[PM-26383] Remove feature flag to enable autoconfirm (#20015)","language":"TypeScript","id":"c2a6f627470bdf03b24f828d7fc922c6b996e272","model-score":0.78,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/web/src/app/vault/services/web-vault-prompt.service.ts b/apps/web/src/app/vault/services/web-vault-prompt.service.ts\nindex fd42d116d1..84dd4d281b 100644\n--- a/apps/web/src/app/vault/services/web-vault-prompt.service.ts\n+++ b/apps/web/src/app/vault/services/web-vault-prompt.service.ts\n@@ -10,4 +10,2 @@ import { AccountService } from \"@bitwarden/common/auth/abstractions/account.serv\n import { getUserId } from \"@bitwarden/common/auth/services/account.service\";\n-import { FeatureFlag } from \"@bitwarden/common/enums/feature-flag.enum\";\n-import { ConfigService } from \"@bitwarden/common/platform/abstractions/config/config.service\";\n import { DialogService } from \"@bitwarden/components\";\n@@ -33,3 +31,2 @@ export class WebVaultPromptService {\n   private organizationService = inject(OrganizationService);\n-  private configService = inject(ConfigService);\n   private dialogService = inject(DialogService);\n@@ -79,4 +76,2 @@ export class WebVaultPromptService {\n \n-    const featureFlag$ = this.configService.getFeatureFlag$(FeatureFlag.AutoConfirm);\n-\n     const autoConfirmState$ = this.userId$.pipe(\n@@ -97,8 +92,7 @@ export class WebVaultPromptService {\n \n-    zip([organization$, featureFlag$, autoConfirmState$, policyEnabled$, this.userId$])\n+    zip([organization$, autoConfirmState$, policyEnabled$, this.userId$])\n       .pipe(\n         first(),\n-        switchMap(async ([organization, flagEnabled, autoConfirmState, policyEnabled, userId]) => {\n+        switchMap(async ([organization, autoConfirmState, policyEnabled, userId]) => {\n           const showDialog =\n-            flagEnabled &&\n             !policyEnabled &&\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vicki League","training-data":{"loc-added":"5","loc-deleted":"5","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"vleague@bitwarden.com","commit-full-message":"","commit-date":"2026-01-16T14:36:00Z","current-rev":"12516ceeea","filename":"clients/libs/components/src/menu/menu-trigger-for.directive.ts","previous-rev":"5dee97158a","commit-title":"[PM-24178] Handle dialog focus when launched from a menu item (#18208)","language":"TypeScript","id":"c30f02246a0aaf050b3444858baeb1d3447a8b8c","model-score":0.77,"author-id":null,"project-id":26215,"delta-file-score":0.59155285,"diff":"diff --git a/libs/components/src/menu/menu-trigger-for.directive.ts b/libs/components/src/menu/menu-trigger-for.directive.ts\nindex 1d79fbc976..6306f3326d 100644\n--- a/libs/components/src/menu/menu-trigger-for.directive.ts\n+++ b/libs/components/src/menu/menu-trigger-for.directive.ts\n@@ -194,3 +194,3 @@ export class MenuTriggerForDirective implements OnDestroy {\n \n-    const escKey = this.overlayRef.keydownEvents().pipe(\n+    const keyEvents = this.overlayRef.keydownEvents().pipe(\n       filter((event: KeyboardEvent) => {\n@@ -204,4 +204,4 @@ export class MenuTriggerForDirective implements OnDestroy {\n     const closeEvents = isContextMenu\n-      ? merge(detachments, escKey, menuClosed)\n-      : merge(detachments, escKey, this.overlayRef.backdropClick(), menuClosed);\n+      ? merge(detachments, keyEvents, menuClosed)\n+      : merge(detachments, keyEvents, this.overlayRef.backdropClick(), menuClosed);\n \n@@ -217,5 +217,5 @@ export class MenuTriggerForDirective implements OnDestroy {\n \n-        if (event instanceof KeyboardEvent && (event.key === \"Tab\" || event.key === \"Escape\")) {\n-          this.elementRef.nativeElement.focus();\n-        }\n+        // Move focus to the menu trigger, since any active menu items are about to be destroyed\n+        this.elementRef.nativeElement.focus();\n+\n         this.destroyMenu();\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Thomas Rittson","training-data":{"loc-added":"2","loc-deleted":"9","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"31796059+eliykat@users.noreply.github.com","commit-full-message":"","commit-date":"2025-05-07T01:23:18Z","current-rev":"df40954b61","filename":"clients/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts","previous-rev":"744c1b1b49","commit-title":"[PM-14613] Remove account deprovisioning feature flag (#14353)","language":"TypeScript","id":"867c26202ff84c026c234cf9b6ca131e28e7f5ea","model-score":0.77,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"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\nindex bc7dff3e70..5df2d7799d 100644\n--- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts\n+++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts\n@@ -154,6 +154,2 @@ export class MemberDialogComponent implements OnDestroy {\n \n-  protected accountDeprovisioningEnabled$: Observable<boolean> = this.configService.getFeatureFlag$(\n-    FeatureFlag.AccountDeprovisioning,\n-  );\n-\n   protected isExternalIdVisible$ = this.configService\n@@ -669,7 +665,5 @@ export class MemberDialogComponent implements OnDestroy {\n       this.deleteManagedMemberWarningService.warningAcknowledged(this.params.organizationId),\n-      this.accountDeprovisioningEnabled$,\n     ]).pipe(\n       map(\n-        ([organization, acknowledged, featureFlagEnabled]) =>\n-          featureFlagEnabled &&\n+        ([organization, acknowledged]) =>\n           organization.canManageUsers &&\n@@ -716,5 +710,4 @@ export class MemberDialogComponent implements OnDestroy {\n \n-    if (await firstValueFrom(this.accountDeprovisioningEnabled$)) {\n-      await this.deleteManagedMemberWarningService.acknowledgeWarning(this.params.organizationId);\n-    }\n+    await this.deleteManagedMemberWarningService.acknowledgeWarning(this.params.organizationId);\n+\n     this.close(MemberDialogResult.Deleted);\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Victoria League","training-data":{"loc-added":"5","loc-deleted":"8","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.096655465156704"},"author-email":"vleague@bitwarden.com","commit-full-message":"","commit-date":"2024-12-27T20:42:35Z","current-rev":"4f060d88fa","filename":"clients/apps/browser/src/popup/services/init.service.ts","previous-rev":"6d65ce9abd","commit-title":"[CL-509][PM-16190] Avoid double scrollbar appearing when default zoom is >100% (#12427)","language":"TypeScript","id":"aebbab9343e0124b2c28f2c63ae59e9dd802d12d","model-score":0.69,"author-id":null,"project-id":26215,"delta-file-score":0.28049663,"diff":"diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts\nindex 9e6471eaf2..2466143849 100644\n--- a/apps/browser/src/popup/services/init.service.ts\n+++ b/apps/browser/src/popup/services/init.service.ts\n@@ -1,3 +1,3 @@\n import { DOCUMENT } from \"@angular/common\";\n-import { Inject, Injectable } from \"@angular/core\";\n+import { inject, Inject, Injectable } from \"@angular/core\";\n \n@@ -12,4 +12,7 @@ import { BrowserApi } from \"../../platform/browser/browser-api\";\n import BrowserPopupUtils from \"../../platform/popup/browser-popup-utils\";\n+import { PopupSizeService } from \"../../platform/popup/layout/popup-size.service\";\n @Injectable()\n export class InitService {\n+  private sizeService = inject(PopupSizeService);\n+\n   constructor(\n@@ -30,9 +33,3 @@ export class InitService {\n \n-      if (!BrowserPopupUtils.inPopup(window)) {\n-        window.document.body.classList.add(\"body-full\");\n-      } else if (window.screen.availHeight < 600) {\n-        window.document.body.classList.add(\"body-xs\");\n-      } else if (window.screen.availHeight <= 800) {\n-        window.document.body.classList.add(\"body-sm\");\n-      }\n+      await this.sizeService.init();\n \n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Todd Martin","training-data":{"loc-added":"2","loc-deleted":"13","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.71","delta-n-functions":"0","current-file-score":"9.842730062691357"},"author-email":"106564991+trmartin4@users.noreply.github.com","commit-full-message":"Co-authored-by: Matt Bishop <mbishop@bitwarden.com>","commit-date":"2024-11-15T17:34:02Z","current-rev":"0308e6e180","filename":"clients/libs/auth/src/angular/login/login.component.ts","previous-rev":"d55c8712ac","commit-title":"Remove showPasswordless conditionals (#11928)","language":"TypeScript","id":"6bf6a9814c081d735966050dfac81c4bf78e9bd7","model-score":0.64,"author-id":null,"project-id":26215,"delta-file-score":0.66286343,"diff":"diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts\nindex 0193e4c403..9f86de8372 100644\n--- a/libs/auth/src/angular/login/login.component.ts\n+++ b/libs/auth/src/angular/login/login.component.ts\n@@ -106,8 +106,2 @@ export class LoginComponent implements OnInit, OnDestroy {\n \n-  /**\n-   * LoginViaAuthRequestSupported is a boolean that determines if we show the Login with device button.\n-   * An AuthRequest is the mechanism that allows users to login to the client via a device that is already logged in.\n-   */\n-  loginViaAuthRequestSupported = false;\n-\n   // Web properties\n@@ -146,3 +140,2 @@ export class LoginComponent implements OnInit, OnDestroy {\n     this.clientType = this.platformUtilsService.getClientType();\n-    this.loginViaAuthRequestSupported = this.loginComponentService.isLoginViaAuthRequestSupported();\n   }\n@@ -404,6 +397,4 @@ export class LoginComponent implements OnInit, OnDestroy {\n \n-      if (this.loginViaAuthRequestSupported) {\n-        // Reset known device state when going back to email entry if it is supported\n-        this.isKnownDevice = false;\n-      }\n+      // Reset known device state when going back to email entry if it is supported\n+      this.isKnownDevice = false;\n     } else if (this.loginUiState === LoginUiState.MASTER_PASSWORD_ENTRY) {\n@@ -428,5 +419,4 @@ export class LoginComponent implements OnInit, OnDestroy {\n \n-      if (this.loginViaAuthRequestSupported) {\n-        await this.getKnownDevice(this.emailFormControl.value);\n-      }\n+      // Check to see if the device is known so we can show the Login with Device option\n+      await this.getKnownDevice(this.emailFormControl.value);\n     }\n@@ -582,5 +572,4 @@ export class LoginComponent implements OnInit, OnDestroy {\n \n-    if (this.loginViaAuthRequestSupported) {\n-      await this.getKnownDevice(this.emailFormControl.value);\n-    }\n+    // Check to see if the device is known so that we can show the Login with Device option\n+    await this.getKnownDevice(this.emailFormControl.value);\n \n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Daniel James Smith","training-data":{"loc-added":"8","loc-deleted":"15","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"2670567+djsmith85@users.noreply.github.com","commit-full-message":"`safeGetString` takes a `string` or `EncString` and return the appropiate value based on it's type\r\n\r\nCo-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>","commit-date":"2024-04-24T21:41:35Z","current-rev":"dba910d0b9","filename":"clients/libs/common/src/models/export/card.export.ts","previous-rev":"a6755f5f20","commit-title":"Create and use `safeGetString()` instead of `instanceof` checks to determine type (#8906)","language":"TypeScript","id":"267ba674f9dcad4c4df46bae64873f6dd7429d73","model-score":0.63,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/libs/common/src/models/export/card.export.ts b/libs/common/src/models/export/card.export.ts\nindex 55bb3a7be1..151b447e86 100644\n--- a/libs/common/src/models/export/card.export.ts\n+++ b/libs/common/src/models/export/card.export.ts\n@@ -4,2 +4,4 @@ import { CardView } from \"../../vault/models/view/card.view\";\n \n+import { safeGetString } from \"./utils\";\n+\n export class CardExport {\n@@ -48,17 +50,8 @@ export class CardExport {\n \n-    if (o instanceof CardView) {\n-      this.cardholderName = o.cardholderName;\n-      this.brand = o.brand;\n-      this.number = o.number;\n-      this.expMonth = o.expMonth;\n-      this.expYear = o.expYear;\n-      this.code = o.code;\n-    } else {\n-      this.cardholderName = o.cardholderName?.encryptedString;\n-      this.brand = o.brand?.encryptedString;\n-      this.number = o.number?.encryptedString;\n-      this.expMonth = o.expMonth?.encryptedString;\n-      this.expYear = o.expYear?.encryptedString;\n-      this.code = o.code?.encryptedString;\n-    }\n+    this.cardholderName = safeGetString(o.cardholderName);\n+    this.brand = safeGetString(o.brand);\n+    this.number = safeGetString(o.number);\n+    this.expMonth = safeGetString(o.expMonth);\n+    this.expYear = safeGetString(o.expYear);\n+    this.code = safeGetString(o.code);\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vincent Salucci","training-data":{"loc-added":"7","loc-deleted":"21","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"26154748+vincentsalucci@users.noreply.github.com","commit-full-message":"* chore: remove fc v1 from org.canEditAnyCollection and update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from collectionView.canEdit and update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from organization.canEditAllCiphers and update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from canDeleteAnyCollection, collection views, update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from canEditUser/GroupAccess, refs PM-10294\r\n\r\n* chore: remove fc v1 from canViewCollectionInfo, refs PM-10294\r\n\r\n* chore: remove fc v1 from account component, refs PM-10294\r\n\r\n* fix: remove fc v1 from collections component, refs PM-10294\r\n\r\n* fix: update vault-items component, refs PM-10294\r\n\r\n* fix: remove fc v1 from collection-dialog and collections components, refs PM-10294\r\n\r\n* chore: remove ConfigService from group-add-edit and account components, refs PM-10294\r\n\r\n* chore: change canEditAnyCollection to getter and update callers, refs PM-10294\r\n\r\n* chore: change canEditUnmanagedCollections to getter and update callers, refs PM-10294\r\n\r\n* chore: change canDeleteAnyCollection to getter and update callers, refs PM-10294\r\n\r\n* chore: remove deprecated observable and update comments with v1, refs PM-10294\r\n\r\n* chore: remove ununsed ConfigService from collection-dialog component, refs PM-10294\r\n\r\n* chore: remove final fc v1 ref for vault-collection-row, refs PM-10294","commit-date":"2024-08-13T15:45:41Z","current-rev":"471dd3bd7b","filename":"clients/libs/common/src/admin-console/models/domain/organization.ts","previous-rev":"43da67ee51","commit-title":"[PM-10294] Remove FC v1 from Clients (#10422)","language":"TypeScript","id":"7f727af1ed624cfdaf8cc2d8a87b9290f138534c","model-score":0.59,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts\nindex dcffe6f158..490c799ad1 100644\n--- a/libs/common/src/admin-console/models/domain/organization.ts\n+++ b/libs/common/src/admin-console/models/domain/organization.ts\n@@ -170,9 +170,4 @@ export class Organization {\n \n-  canEditAnyCollection(flexibleCollectionsV1Enabled: boolean) {\n-    if (!flexibleCollectionsV1Enabled) {\n-      // Pre-Flexible Collections v1 logic\n-      return this.isAdmin || this.permissions.editAnyCollection;\n-    }\n-\n-    // Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins\n+  get canEditAnyCollection() {\n+    // The allowAdminAccessToAllCollectionItems flag can restrict admins\n     // Providers and custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag\n@@ -185,3 +180,3 @@ export class Organization {\n \n-  canEditUnmanagedCollections() {\n+  get canEditUnmanagedCollections() {\n     // Any admin or custom user with editAnyCollection permission can edit unmanaged collections\n@@ -205,11 +200,3 @@ export class Organization {\n \n-  canEditAllCiphers(\n-    flexibleCollectionsV1Enabled: boolean,\n-    restrictProviderAccessFlagEnabled: boolean,\n-  ) {\n-    // Before Flexible Collections V1, any admin or anyone with editAnyCollection permission could edit all ciphers\n-    if (!flexibleCollectionsV1Enabled) {\n-      return this.isAdmin || this.permissions.editAnyCollection;\n-    }\n-\n+  canEditAllCiphers(restrictProviderAccessFlagEnabled: boolean) {\n     // Providers can access items until the restrictProviderAccess flag is enabled\n@@ -221,3 +208,3 @@ export class Organization {\n \n-    // Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins\n+    // The allowAdminAccessToAllCollectionItems flag can restrict admins\n     // Custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag\n@@ -231,6 +218,5 @@ export class Organization {\n   /**\n-   * @param flexibleCollectionsV1Enabled - Whether or not the V1 Flexible Collection feature flag is enabled\n    * @returns True if the user can delete any collection\n    */\n-  canDeleteAnyCollection(flexibleCollectionsV1Enabled: boolean) {\n+  get canDeleteAnyCollection() {\n     // Providers and Users with DeleteAnyCollection permission can always delete collections\n@@ -242,3 +228,3 @@ export class Organization {\n     // Using explicit type checks because provider users are handled above and this mimics the server's permission checks closely\n-    if (!flexibleCollectionsV1Enabled || this.allowAdminAccessToAllCollectionItems) {\n+    if (this.allowAdminAccessToAllCollectionItems) {\n       return this.type == OrganizationUserType.Owner || this.type == OrganizationUserType.Admin;\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"cd-bitwarden","training-data":{"loc-added":"1","loc-deleted":"12","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"106776772+cd-bitwarden@users.noreply.github.com","commit-full-message":"* Removing feature flag\r\n\r\n* Removing flag from feature-flag.enum.ts\r\n\r\n* suggested changes\r\n\r\n* prettier\r\n\r\n* fixing merge conflict issue\r\n\r\n* Removing unused code\r\n\r\n* suggested change from Gbubemi\r\n\r\n* Adding back merge conflict code\r\n\r\n* fixing prettier styling","commit-date":"2024-10-02T16:55:54Z","current-rev":"a23991a64b","filename":"clients/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts","previous-rev":"21a4b48eca","commit-title":"[pm-10995] feature flag removal (#11000)","language":"TypeScript","id":"b5e9bd19d81d99af873cf9b5662ab33645ff545f","model-score":0.58,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts\nindex 72d6a57aad..3b44662ecd 100644\n--- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts\n+++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts\n@@ -37,3 +37,2 @@ export class VaultCipherRowComponent implements OnInit {\n   @Input() canEditCipher: boolean;\n-  @Input() vaultBulkManagementActionEnabled: boolean;\n \n@@ -102,13 +101,11 @@ export class VaultCipherRowComponent implements OnInit {\n   protected get disableMenu() {\n-    return (\n-      !(\n-        this.isNotDeletedLoginCipher ||\n-        this.showCopyPassword ||\n-        this.showCopyTotp ||\n-        this.showLaunchUri ||\n-        this.showAttachments ||\n-        this.showClone ||\n-        this.canEditCipher ||\n-        this.cipher.isDeleted\n-      ) && this.vaultBulkManagementActionEnabled\n+    return !(\n+      this.isNotDeletedLoginCipher ||\n+      this.showCopyPassword ||\n+      this.showCopyTotp ||\n+      this.showLaunchUri ||\n+      this.showAttachments ||\n+      this.showClone ||\n+      this.canEditCipher ||\n+      this.cipher.isDeleted\n     );\n@@ -124,10 +121,2 @@ export class VaultCipherRowComponent implements OnInit {\n \n-  protected moveToOrganization() {\n-    this.onEvent.emit({ type: \"moveToOrganization\", items: [this.cipher] });\n-  }\n-\n-  protected editCollections() {\n-    this.onEvent.emit({ type: \"viewCipherCollections\", item: this.cipher });\n-  }\n-\n   protected events() {\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nick Krantz","training-data":{"loc-added":"8","loc-deleted":"0","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.096655465156704"},"author-email":"125900171+nick-livefront@users.noreply.github.com","commit-full-message":"* add exclamation badge for at risk passwords on tab\n\n* add berry icon for the badge when pending tasks are present\n\n* remove integration wtih autofill for pending task badge\n\n* add ability to override Never match strategy\n- This is helpful for non-autofill purposes but cipher matching is still needed. This will default to the domain.\n\n* add at-risk-cipher badge updater service\n\n* Revert \"add exclamation badge for at risk passwords on tab\"\n\nThis reverts commit a9643c03d5ff812a88d554b4a4bcb13f0d5444f0.\n\n* remove nullish-coalescing\n\n* ensure that all user related observables use the same user.id\n\n---------\n\nCo-authored-by: Shane Melton <smelton@bitwarden.com>","commit-date":"2025-09-02T20:09:20Z","current-rev":"5967cf0539","filename":"clients/libs/common/src/vault/models/view/login-uri.view.ts","previous-rev":"a4fca832f3","commit-title":"[PM-14571] At Risk Passwords - Badge Update (#15983)","language":"TypeScript","id":"a1310fb3083672373a1aa86df05ae2a0c330bbb2","model-score":0.57,"author-id":null,"project-id":26215,"delta-file-score":0.28049663,"diff":"diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts\nindex 38cd517e54..49ac9c6278 100644\n--- a/libs/common/src/vault/models/view/login-uri.view.ts\n+++ b/libs/common/src/vault/models/view/login-uri.view.ts\n@@ -144,2 +144,4 @@ export class LoginUriView implements View {\n     defaultUriMatch: UriMatchStrategySetting = null,\n+    /** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */\n+    overrideNeverMatchStrategy?: true,\n   ): boolean {\n@@ -152,2 +154,8 @@ export class LoginUriView implements View {\n \n+    // Override the match strategy with `Domain` when it is `Never` and `overrideNeverMatchStrategy` is true.\n+    // This is useful in scenarios when the cipher should be matched to rely other information other than autofill.\n+    if (overrideNeverMatchStrategy && matchType === UriMatchStrategy.Never) {\n+      matchType = UriMatchStrategy.Domain;\n+    }\n+\n     const targetDomain = Utils.getDomain(targetUri);\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Maciej Zieniuk","training-data":{"loc-added":"17","loc-deleted":"19","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.842730062691357"},"author-email":"167752252+mzieniukbw@users.noreply.github.com","commit-full-message":"* enforce session timeout policy\n\n* better angular validation\n\n* lint fix\n\n* missing switch break\n\n* fallback when timeout not supported with highest available timeout\n\n* failing unit tests\n\n* incorrect policy message\n\n* vault timeout type adjustments\n\n* fallback to \"on browser refresh\" for browser, when policy is set to \"on system locked\", but not available (Safari)\n\n* docs, naming improvements\n\n* fallback for current user session timeout to \"on refresh\", when policy is set to \"on system locked\", but not available.\n\n* don't display policy message when the policy does not affect available timeout options\n\n* 8 hours default when changing from non-numeric timeout to Custom.\n\n* failing unit test\n\n* missing locales, changing functions access to private, docs\n\n* removal of redundant magic number\n\n* missing await\n\n* await once for available timeout options\n\n* adjusted messaging\n\n* unit test coverage\n\n* vault timeout numeric module exports\n\n* unit test coverage","commit-date":"2025-12-05T13:55:59Z","current-rev":"bbea11388a","filename":"clients/bitwarden_license/bit-web/src/app/key-management/policies/session-timeout.component.ts","previous-rev":"c036ffd775","commit-title":"[PM-26057] Enforce session timeout policy (#17424)","language":"TypeScript","id":"5b3318d86300afeff3dd125f0d299dca4e780cb4","model-score":0.56,"author-id":null,"project-id":26215,"delta-file-score":0.3063433,"diff":"diff --git a/bitwarden_license/bit-web/src/app/key-management/policies/session-timeout.component.ts b/bitwarden_license/bit-web/src/app/key-management/policies/session-timeout.component.ts\nindex 9c6129f64d..fd541d3040 100644\n--- a/bitwarden_license/bit-web/src/app/key-management/policies/session-timeout.component.ts\n+++ b/bitwarden_license/bit-web/src/app/key-management/policies/session-timeout.component.ts\n@@ -12,2 +12,7 @@ import {\n import { PolicyType } from \"@bitwarden/common/admin-console/enums\";\n+import {\n+  MaximumSessionTimeoutPolicyData,\n+  SessionTimeoutAction,\n+  SessionTimeoutType,\n+} from \"@bitwarden/common/key-management/session-timeout\";\n import { VaultTimeoutAction } from \"@bitwarden/common/key-management/vault-timeout\";\n@@ -16,4 +21,4 @@ import { DialogService } from \"@bitwarden/components\";\n import {\n-  BasePolicyEditDefinition,\n   BasePolicyEditComponent,\n+  BasePolicyEditDefinition,\n } from \"@bitwarden/web-vault/app/admin-console/organizations/policies\";\n@@ -23,11 +28,2 @@ import { SessionTimeoutConfirmationNeverComponent } from \"./session-timeout-conf\n \n-export type SessionTimeoutAction = null | \"lock\" | \"logOut\";\n-export type SessionTimeoutType =\n-  | null\n-  | \"never\"\n-  | \"onAppRestart\"\n-  | \"onSystemLock\"\n-  | \"immediately\"\n-  | \"custom\";\n-\n export class SessionTimeoutPolicy extends BasePolicyEditDefinition {\n@@ -52,5 +48,2 @@ export class SessionTimeoutPolicyComponent\n {\n-  private destroy$ = new Subject<void>();\n-  private lastConfirmedType$ = new BehaviorSubject<SessionTimeoutType>(null);\n-\n   actionOptions: { name: string; value: SessionTimeoutAction }[];\n@@ -76,2 +69,5 @@ export class SessionTimeoutPolicyComponent\n \n+  private destroy$ = new Subject<void>();\n+  private lastConfirmedType$ = new BehaviorSubject<SessionTimeoutType>(null);\n+\n   constructor(\n@@ -125,8 +121,6 @@ export class SessionTimeoutPolicyComponent\n   protected override loadData() {\n-    const minutes: number | null = this.policyResponse?.data?.minutes ?? null;\n-    const action: SessionTimeoutAction =\n-      this.policyResponse?.data?.action ?? (null satisfies SessionTimeoutAction);\n+    const minutes: number | null = this.policyData?.minutes ?? null;\n+    const action: SessionTimeoutAction = this.policyData?.action ?? null;\n     // For backward compatibility, the \"type\" field might not exist, hence we initialize it based on the presence of \"minutes\"\n-    const type: SessionTimeoutType =\n-      this.policyResponse?.data?.type ?? ((minutes ? \"custom\" : null) satisfies SessionTimeoutType);\n+    const type: SessionTimeoutType = this.policyData?.type ?? (minutes ? \"custom\" : null);\n \n@@ -167,3 +161,7 @@ export class SessionTimeoutPolicyComponent\n       action: this.data.value.action,\n-    };\n+    } satisfies MaximumSessionTimeoutPolicyData;\n+  }\n+\n+  private get policyData(): MaximumSessionTimeoutPolicyData | null {\n+    return this.policyResponse?.data ?? null;\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Leslie Tilton","training-data":{"loc-added":"10","loc-deleted":"21","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"23057410+Banrion@users.noreply.github.com","commit-full-message":"* feat(access-intelligence): hookup trend widget and add missing properties to summary\n\n* Fix graph looking at non critical numbers\n\n* Swap at risk members card and password change metric widget","commit-date":"2026-03-24T19:30:44Z","current-rev":"3b5b784d10","filename":"clients/bitwarden_license/bit-common/src/dirt/access-intelligence/models/api/access-report-summary.api.ts","previous-rev":"8abdd081b4","commit-title":"[PM-32057] Wire up Trend Widget in Access Intelligence Activity (#19664)","language":"TypeScript","id":"39fd9854b0cc1368983b8a923a0d29d6c2f130ed","model-score":0.54,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/bitwarden_license/bit-common/src/dirt/access-intelligence/models/api/access-report-summary.api.ts b/bitwarden_license/bit-common/src/dirt/access-intelligence/models/api/access-report-summary.api.ts\nindex c64d451414..9d0c437642 100644\n--- a/bitwarden_license/bit-common/src/dirt/access-intelligence/models/api/access-report-summary.api.ts\n+++ b/bitwarden_license/bit-common/src/dirt/access-intelligence/models/api/access-report-summary.api.ts\n@@ -10,3 +10,3 @@ import { AccessReportSummaryView } from \"../view/access-report-summary.view\";\n /**\n- * Converts an AccessReportSummary API response\n+ * API response model for an encrypted access report summary entry.\n  *\n@@ -14,13 +14,9 @@ import { AccessReportSummaryView } from \"../view/access-report-summary.view\";\n  * - See {@link AccessReportSummaryData} for data model\n- * - See {@link AccessReportSummaryView} from View Model\n+ * - See {@link AccessReportSummaryView} for view model\n  */\n export class AccessReportSummaryApi extends BaseResponse {\n-  totalMemberCount: number = 0;\n-  totalApplicationCount: number = 0;\n-  totalAtRiskMemberCount: number = 0;\n-  totalAtRiskApplicationCount: number = 0;\n-  totalCriticalApplicationCount: number = 0;\n-  totalCriticalMemberCount: number = 0;\n-  totalCriticalAtRiskMemberCount: number = 0;\n-  totalCriticalAtRiskApplicationCount: number = 0;\n+  organizationId: string = \"\";\n+  encryptedData: string = \"\";\n+  encryptionKey: string = \"\";\n+  date: string = \"\";\n \n@@ -29,13 +25,6 @@ export class AccessReportSummaryApi extends BaseResponse {\n \n-    this.totalMemberCount = this.getResponseProperty(\"totalMemberCount\") || 0;\n-    this.totalApplicationCount = this.getResponseProperty(\"totalApplicationCount\") || 0;\n-    this.totalAtRiskMemberCount = this.getResponseProperty(\"totalAtRiskMemberCount\") || 0;\n-    this.totalAtRiskApplicationCount = this.getResponseProperty(\"totalAtRiskApplicationCount\") || 0;\n-    this.totalCriticalApplicationCount =\n-      this.getResponseProperty(\"totalCriticalApplicationCount\") || 0;\n-    this.totalCriticalMemberCount = this.getResponseProperty(\"totalCriticalMemberCount\") || 0;\n-    this.totalCriticalAtRiskMemberCount =\n-      this.getResponseProperty(\"totalCriticalAtRiskMemberCount\") || 0;\n-    this.totalCriticalAtRiskApplicationCount =\n-      this.getResponseProperty(\"totalCriticalAtRiskApplicationCount\") || 0;\n+    this.organizationId = this.getResponseProperty(\"OrganizationId\") ?? \"\";\n+    this.encryptedData = this.getResponseProperty(\"EncryptedData\") ?? \"\";\n+    this.encryptionKey = this.getResponseProperty(\"EncryptionKey\") ?? \"\";\n+    this.date = this.getResponseProperty(\"Date\") ?? \"\";\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Matt Gibson","training-data":{"loc-added":"1","loc-deleted":"23","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mgibson@bitwarden.com","commit-full-message":"* remove active account unlocked from state service\r\n\r\n* Remove status from account service `AccountInfo`\r\n\r\n* Fixup lingering usages of status\r\n\r\nFixup missed factories\r\n\r\n* Fixup account info usage\r\n\r\n* fixup CLI build\r\n\r\n* Fixup current account type\r\n\r\n* Add helper for all auth statuses to auth service\r\n\r\n* Fix tests\r\n\r\n* Uncomment mistakenly commented code\r\n\r\n* Rework logged out account exclusion tests\r\n\r\n* Correct test description\r\n\r\n* Avoid getters returning observables\r\n\r\n* fixup type","commit-date":"2024-04-12T07:25:45Z","current-rev":"8d698d9d84","filename":"clients/libs/common/src/auth/abstractions/account.service.ts","previous-rev":"c7ea35280d","commit-title":"[PM-7169][PM-5267] Remove auth status from account info (#8539)","language":"TypeScript","id":"9fb72a4d21dee7215bb08e2fc97940101822c66f","model-score":0.51,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/libs/common/src/auth/abstractions/account.service.ts b/libs/common/src/auth/abstractions/account.service.ts\nindex 4e2a462755..fa9ad36378 100644\n--- a/libs/common/src/auth/abstractions/account.service.ts\n+++ b/libs/common/src/auth/abstractions/account.service.ts\n@@ -3,3 +3,2 @@ import { Observable } from \"rxjs\";\n import { UserId } from \"../../types/guid\";\n-import { AuthenticationStatus } from \"../enums/authentication-status\";\n \n@@ -10,3 +9,2 @@ import { AuthenticationStatus } from \"../enums/authentication-status\";\n export type AccountInfo = {\n-  status: AuthenticationStatus;\n   email: string;\n@@ -16,3 +14,3 @@ export type AccountInfo = {\n export function accountInfoEqual(a: AccountInfo, b: AccountInfo) {\n-  return a?.status === b?.status && a?.email === b?.email && a?.name === b?.name;\n+  return a?.email === b?.email && a?.name === b?.name;\n }\n@@ -22,4 +20,2 @@ export abstract class AccountService {\n   activeAccount$: Observable<{ id: UserId | undefined } & AccountInfo>;\n-  accountLock$: Observable<UserId>;\n-  accountLogout$: Observable<UserId>;\n   /**\n@@ -42,20 +38,2 @@ export abstract class AccountService {\n   abstract setAccountEmail(userId: UserId, email: string): Promise<void>;\n-  /**\n-   * Updates the `accounts$` observable with the new account status.\n-   * Also emits the `accountLock$` or `accountLogout$` observable if the status is `Locked` or `LoggedOut` respectively.\n-   * @param userId\n-   * @param status\n-   */\n-  abstract setAccountStatus(userId: UserId, status: AuthenticationStatus): Promise<void>;\n-  /**\n-   * Updates the `accounts$` observable with the new account status if the current status is higher than the `maxStatus`.\n-   *\n-   * This method only downgrades status to the maximum value sent in, it will not increase authentication status.\n-   *\n-   * @example An account is transitioning from unlocked to logged out. If callbacks that set the status to locked occur\n-   * after it is updated to logged out, the account will be in the incorrect state.\n-   * @param userId The user id of the account to be updated.\n-   * @param maxStatus The new status of the account.\n-   */\n-  abstract setMaxAccountStatus(userId: UserId, maxStatus: AuthenticationStatus): Promise<void>;\n   /**\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Maximilian Power","training-data":{"loc-added":"7","loc-deleted":"13","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"8.545379580978913"},"author-email":"mpower@bitwarden.com","commit-full-message":"* updated strings","commit-date":"2025-11-17T16:50:39Z","current-rev":"16e4eb1dd0","filename":"clients/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts","previous-rev":"a2abbd09bf","commit-title":"updates strings (#17422)","language":"TypeScript","id":"142dfe5a49a579a1765e7ed0de40ae5a1871e606","model-score":0.51,"author-id":null,"project-id":26215,"delta-file-score":0.2613985,"diff":"diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts\nindex eddc26cbc7..9e6901572c 100644\n--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts\n+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts\n@@ -12,3 +12,3 @@ import { takeUntilDestroyed } from \"@angular/core/rxjs-interop\";\n import { ActivatedRoute, Router } from \"@angular/router\";\n-import { combineLatest, EMPTY, firstValueFrom } from \"rxjs\";\n+import { EMPTY, firstValueFrom } from \"rxjs\";\n import { distinctUntilChanged, map, tap } from \"rxjs/operators\";\n@@ -86,10 +86,7 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {\n \n-  // Empty state properties\n-  protected organizationName = \"\";\n-\n   // Empty state computed properties\n   protected emptyStateBenefits: [string, string][] = [\n-    [this.i18nService.t(\"benefit1Title\"), this.i18nService.t(\"benefit1Description\")],\n-    [this.i18nService.t(\"benefit2Title\"), this.i18nService.t(\"benefit2Description\")],\n-    [this.i18nService.t(\"benefit3Title\"), this.i18nService.t(\"benefit3Description\")],\n+    [this.i18nService.t(\"feature1Title\"), this.i18nService.t(\"feature1Description\")],\n+    [this.i18nService.t(\"feature2Title\"), this.i18nService.t(\"feature2Description\")],\n+    [this.i18nService.t(\"feature3Title\"), this.i18nService.t(\"feature3Description\")],\n   ];\n@@ -142,7 +139,7 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {\n \n-    // Combine report data, vault items check, organization details, and generation state\n+    // Subscribe to report data updates\n     // This declarative pattern ensures proper cleanup and prevents memory leaks\n-    combineLatest([this.dataService.enrichedReportData$, this.dataService.organizationDetails$])\n+    this.dataService.enrichedReportData$\n       .pipe(takeUntilDestroyed(this.destroyRef))\n-      .subscribe(([report, orgDetails]) => {\n+      .subscribe((report) => {\n         // Update report state\n@@ -150,5 +147,2 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {\n         this.dataLastUpdated = report?.creationDate ?? null;\n-\n-        // Update organization name\n-        this.organizationName = orgDetails?.organizationName ?? \"\";\n       });\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Cesar Gonzalez","training-data":{"loc-added":"40","loc-deleted":"20","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"cesar.a.gonzalezcs@gmail.com","commit-full-message":"* [PM-934] Autofill not working until page has been refreshed\r\n\r\n* [PM-934] Adjusting cleanup of the messages_handler script\r\n\r\n* [PM-934] Fixing small issue found within collection of page details\r\n\r\n* [PM-934] Addressing concenrs brought up during code review\r\n\r\n* [PM-934] Addressing concenrs brought up during code review\r\n\r\n* [PM-934] Addressing concenrs brought up during code review\r\n\r\n* [PM-934] Addressing concenrs brought up during code review\r\n\r\n* [PM-934] Applying re-set changes to the autofill overlay implementation on reset of the extension\r\n\r\n* [PM-934] Applying jest tests to added logic within AutofillOverlayContent service\r\n\r\n* [PM-934] Fixing typo present in tabs background listener\r\n\r\n* [PM-934] Finishing up jest tests for updated implementation\r\n\r\n* [PM-934] Incorporating methodology for ensuring the autofill overlay updates to reflect user settings within existing tabs\r\n\r\n* [PM-934] Refining implementation to ensure we do not unnecessarily re-inject content scripts when the autofill overlay settings change\r\n\r\n* [PM-934] Working through jest tests for added implementation details\r\n\r\n* [PM-934] Working through jest tests for added implementation details\r\n\r\n* [PM-934] Finalizing jest tests for implemented logic\r\n\r\n* [PM-5035] Refactoring method structure","commit-date":"2023-12-13T16:25:16Z","current-rev":"bf60711efe","filename":"clients/apps/browser/src/autofill/content/autofiller.ts","previous-rev":"7051f255ed","commit-title":"[PM-934] Autofill not working until page has been refreshed (#6826)","language":"TypeScript","id":"4187d50636f89a8190e2a4d9d4952944277ffb55","model-score":0.49,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/browser/src/autofill/content/autofiller.ts b/apps/browser/src/autofill/content/autofiller.ts\nindex 7f58e72c7d..c3a2f7f579 100644\n--- a/apps/browser/src/autofill/content/autofiller.ts\n+++ b/apps/browser/src/autofill/content/autofiller.ts\n@@ -1 +1,3 @@\n+import { getFromLocalStorage, setupExtensionDisconnectAction } from \"../utils\";\n+\n if (document.readyState === \"loading\") {\n@@ -10,23 +12,26 @@ function loadAutofiller() {\n   let delayFillTimeout: number;\n-\n-  const activeUserIdKey = \"activeUserId\";\n-  let activeUserId: string;\n-\n-  chrome.storage.local.get(activeUserIdKey, (obj: any) => {\n-    if (obj == null || obj[activeUserIdKey] == null) {\n-      return;\n+  let doFillInterval: NodeJS.Timeout;\n+  const handleExtensionDisconnect = () => {\n+    clearDoFillInterval();\n+    clearDelayFillTimeout();\n+  };\n+  const handleExtensionMessage = (message: any) => {\n+    if (message.command === \"fillForm\" && pageHref === message.url) {\n+      filledThisHref = true;\n     }\n-    activeUserId = obj[activeUserIdKey];\n-  });\n+  };\n \n-  chrome.storage.local.get(activeUserId, (obj: any) => {\n-    if (obj?.[activeUserId]?.settings?.enableAutoFillOnPageLoad === true) {\n-      setInterval(() => doFillIfNeeded(), 500);\n-    }\n-  });\n-  chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {\n-    if (msg.command === \"fillForm\" && pageHref === msg.url) {\n-      filledThisHref = true;\n+  setupExtensionEventListeners();\n+  triggerUserFillOnLoad();\n+\n+  async function triggerUserFillOnLoad() {\n+    const activeUserIdKey = \"activeUserId\";\n+    const userKeyStorage = await getFromLocalStorage(activeUserIdKey);\n+    const activeUserId = userKeyStorage[activeUserIdKey];\n+    const activeUserStorage = await getFromLocalStorage(activeUserId);\n+    if (activeUserStorage?.[activeUserId]?.settings?.enableAutoFillOnPageLoad === true) {\n+      clearDoFillInterval();\n+      doFillInterval = setInterval(() => doFillIfNeeded(), 500);\n     }\n-  });\n+  }\n \n@@ -38,5 +43,3 @@ function loadAutofiller() {\n         filledThisHref = false;\n-        if (delayFillTimeout != null) {\n-          window.clearTimeout(delayFillTimeout);\n-        }\n+        clearDelayFillTimeout();\n         delayFillTimeout = window.setTimeout(() => {\n@@ -57,2 +60,19 @@ function loadAutofiller() {\n   }\n+\n+  function clearDoFillInterval() {\n+    if (doFillInterval) {\n+      window.clearInterval(doFillInterval);\n+    }\n+  }\n+\n+  function clearDelayFillTimeout() {\n+    if (delayFillTimeout) {\n+      window.clearTimeout(delayFillTimeout);\n+    }\n+  }\n+\n+  function setupExtensionEventListeners() {\n+    setupExtensionDisconnectAction(handleExtensionDisconnect);\n+    chrome.runtime.onMessage.addListener(handleExtensionMessage);\n+  }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Bernd Schoolmann","training-data":{"loc-added":"1","loc-deleted":"7","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"8.545379580978913"},"author-email":"mail@quexten.com","commit-full-message":"","commit-date":"2025-12-03T12:11:03Z","current-rev":"a6100d8a0e","filename":"clients/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts","previous-rev":"cf416388d7","commit-title":"Replace webcrypto RSA with PureCrypto RSA (#17742)","language":"TypeScript","id":"aa52dee2cff8bc56a23a3801610da85efb1e2f3d","model-score":0.48,"author-id":null,"project-id":26215,"delta-file-score":0.2613985,"diff":"diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts\nindex 132bbc306c..a5da0c8238 100644\n--- a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts\n+++ b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts\n@@ -254,3 +254,2 @@ export class EncryptServiceImplementation implements EncryptService {\n \n-    let algorithm: \"sha1\" | \"sha256\";\n     switch (data.encryptionType) {\n@@ -258,7 +257,2 @@ export class EncryptServiceImplementation implements EncryptService {\n       case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:\n-        algorithm = \"sha1\";\n-        break;\n-      case EncryptionType.Rsa2048_OaepSha256_B64:\n-      case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:\n-        algorithm = \"sha256\";\n         break;\n@@ -272,3 +266,3 @@ export class EncryptServiceImplementation implements EncryptService {\n \n-    return this.cryptoFunctionService.rsaDecrypt(data.dataBytes, privateKey, algorithm);\n+    return this.cryptoFunctionService.rsaDecrypt(data.dataBytes, privateKey, \"sha1\");\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Matt Gibson","training-data":{"loc-added":"6","loc-deleted":"24","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"mgibson@bitwarden.com","commit-full-message":"* Remove getbgService for crypto service\r\n\r\n* Remove special authentication for state service\r\n\r\n* Use synced memory storage\r\n\r\npopup contexts use foreground, background contexts use background. Simple\r\n\r\n* Remove private mode warnings","commit-date":"2024-05-01T11:59:30Z","current-rev":"7e9ab6a15b","filename":"clients/apps/browser/src/background/main.background.ts","previous-rev":"b4631b0dd1","commit-title":"[PM-7807][PM-7617] [PM-6185] Firefox private mode out of experimentation (#8921)","language":"TypeScript","id":"8bc862cb03f6cf797314e2c09398413781e6f835","model-score":0.46,"author-id":null,"project-id":26215,"delta-file-score":0.8720495,"diff":"diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts\nindex adcb4a21ba..16b8c8beea 100644\n--- a/apps/browser/src/background/main.background.ts\n+++ b/apps/browser/src/background/main.background.ts\n@@ -111,3 +111,2 @@ import { FileUploadService } from \"@bitwarden/common/platform/services/file-uplo\n import { KeyGenerationService } from \"@bitwarden/common/platform/services/key-generation.service\";\n-import { MemoryStorageService } from \"@bitwarden/common/platform/services/memory-storage.service\";\n import { MigrationBuilderService } from \"@bitwarden/common/platform/services/migration-builder.service\";\n@@ -358,6 +357,3 @@ export default class MainBackground {\n \n-  constructor(\n-    public isPrivateMode: boolean = false,\n-    public popupOnlyContext: boolean = false,\n-  ) {\n+  constructor(public popupOnlyContext: boolean = false) {\n     // Services\n@@ -445,6 +441,10 @@ export default class MainBackground {\n       ? new BrowserMemoryStorageService() // mv3 stores to storage.session\n-      : new BackgroundMemoryStorageService(); // mv2 stores to memory\n+      : popupOnlyContext\n+        ? new ForegroundMemoryStorageService()\n+        : new BackgroundMemoryStorageService(); // mv2 stores to memory\n     this.memoryStorageService = BrowserApi.isManifestVersion(3)\n       ? this.memoryStorageForStateProviders // manifest v3 can reuse the same storage. They are split for v2 due to lacking a good sync mechanism, which isn't true for v3\n-      : new MemoryStorageService();\n+      : popupOnlyContext\n+        ? new ForegroundMemoryStorageService()\n+        : new BackgroundMemoryStorageService();\n     this.largeObjectMemoryStorageForStateProviders = BrowserApi.isManifestVersion(3)\n@@ -1111,23 +1111,5 @@ export default class MainBackground {\n \n-    if (this.platformUtilsService.isFirefox() && !this.isPrivateMode) {\n-      // Set Private Mode windows to the default icon - they do not share state with the background page\n-      const privateWindows = await BrowserApi.getPrivateModeWindows();\n-      privateWindows.forEach(async (win) => {\n-        await new UpdateBadge(self).setBadgeIcon(\"\", win.id);\n-      });\n-\n-      // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.\n-      // eslint-disable-next-line @typescript-eslint/no-floating-promises\n-      BrowserApi.onWindowCreated(async (win) => {\n-        if (win.incognito) {\n-          await new UpdateBadge(self).setBadgeIcon(\"\", win.id);\n-        }\n-      });\n-    }\n-\n     return new Promise<void>((resolve) => {\n       setTimeout(async () => {\n-        if (!this.isPrivateMode) {\n-          await this.refreshBadge();\n-        }\n+        await this.refreshBadge();\n         await this.fullSync(true);\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Leslie Tilton","training-data":{"loc-added":"7","loc-deleted":"33","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"23057410+Banrion@users.noreply.github.com","commit-full-message":"* Update type guard for cipher ids on reports\n\n* Update report model cipher id type and mock data\n\n* Update security tasks api service to have copied getAllTasks function from the vault team\n\n* Expose critical application at risk cipher ids\n\n* Update cipher id type in report service. Update all activities service to move task function to task service\n\n* Update module\n\n* Update organization id sharing through components instead of multiple route fetchings\n\n* Update view type of password change widget. Update variables to be signals. Refactor logic for calculations based on individual tasks\n\n* Update usage of request password change function\n\n* Update security tasks service to manage tasks\n\n* Remove unused variable\n\n* Alphabetized functions, added documentation. Removed injectable decorator\n\n* Alphabetize constructor params for password health service\n\n* Update providers\n\n* Address NaN case on percentage. Address obsolete type casting to CipherID and any other claude comments\n\n* Fix dependency array in test case","commit-date":"2025-11-07T18:04:05Z","current-rev":"ec07a5391a","filename":"clients/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts","previous-rev":"4f9ae78598","commit-title":"[PM-27762] Activity Tab - Password Change Progress - Assign tasks for new passwords  (#17268)","language":"TypeScript","id":"bb9d80f8967efc0e9dcb92f2d46a3821c16e1d50","model-score":0.44,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts\nindex 8a1a90245b..e415fbf9ad 100644\n--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts\n+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts\n@@ -10,3 +10,3 @@ import {\n import { takeUntilDestroyed } from \"@angular/core/rxjs-interop\";\n-import { from, switchMap } from \"rxjs\";\n+import { from, switchMap, take } from \"rxjs\";\n \n@@ -14,4 +14,2 @@ import {\n   ApplicationHealthReportDetail,\n-  ApplicationHealthReportDetailEnriched,\n-  OrganizationReportApplication,\n   RiskInsightsDataService,\n@@ -211,36 +209,12 @@ export class NewApplicationsDialogComponent {\n \n-    // Create updated organization report application types with new review date\n-    // and critical marking based on selected applications\n-    const newReviewDate = new Date();\n-    const updatedApplications: OrganizationReportApplication[] =\n-      this.dialogParams.newApplications.map((app) => ({\n-        applicationName: app.applicationName,\n-        isCritical: this.selectedApplications().has(app.applicationName),\n-        reviewedDate: newReviewDate,\n-      }));\n-\n     // Save the application review dates and critical markings\n-    this.dataService\n-      .saveApplicationReviewStatus(updatedApplications)\n+    this.dataService.criticalApplicationAtRiskCipherIds$\n       .pipe(\n-        takeUntilDestroyed(this.destroyRef),\n-        switchMap((updatedState) => {\n-          // After initial save is complete, created the assigned tasks\n-          // for at risk passwords\n-          const updatedStateApplicationData = updatedState?.data?.applicationData || [];\n-          // Manual enrich for type matching\n-          // TODO Consolidate in model updates\n-          const manualEnrichedApplications =\n-            updatedState?.data?.reportData.map(\n-              (application): ApplicationHealthReportDetailEnriched => ({\n-                ...application,\n-                isMarkedAsCritical: updatedStateApplicationData.some(\n-                  (a) => a.applicationName == application.applicationName && a.isCritical,\n-                ),\n-              }),\n-            ) || [];\n+        takeUntilDestroyed(this.destroyRef), // Satisfy eslint rule\n+        take(1), // Handle unsubscribe for one off operation\n+        switchMap((criticalApplicationAtRiskCipherIds) => {\n           return from(\n-            this.accessIntelligenceSecurityTasksService.assignTasks(\n+            this.accessIntelligenceSecurityTasksService.requestPasswordChangeForCriticalApplications(\n               this.dialogParams.organizationId,\n-              manualEnrichedApplications,\n+              criticalApplicationAtRiskCipherIds,\n             ),\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vincent Salucci","training-data":{"loc-added":"4","loc-deleted":"47","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"26154748+vincentsalucci@users.noreply.github.com","commit-full-message":"* chore: organization.ts, remove refs to flexibleCollections and isManager, refs AC-2647\r\n\r\n* chore: clean up callers of removed methods from organization.ts, refs AC-2647\r\n\r\n* chore: access-selector, remove fc input and update permissionList param, refs AC-2647\r\n\r\n* chore: update permissionList caller, update group-add-edit fc refs, and remove accessAll, refs AC-2647\r\n\r\n* chore: update member-dialog fc callers, refs AC-2647\r\n\r\n* chore: update bulk-collections-dialog fc callers, refs AC-2647\r\n\r\n* chore: update collection-dialog fc callers, refs AC-2647\r\n\r\n* chore: update simple fc caller to misc files, refs AC-2647\r\n\r\n* chore: update member-dialog fc callers, refs AC-2647\r\n\r\n* chore: remove accessAll references and update callers, refs AC-2647\r\n\r\n* chore: update comment to specify v1 usage, refs AC-2647\r\n\r\n* chore: remove unused message keys and code calls to use those messages, refs AC-2647\r\n\r\n* chore: remove readonly false from access-selector model map function, refs AC-2647","commit-date":"2024-06-10T16:59:20Z","current-rev":"b169207b74","filename":"clients/libs/common/src/admin-console/models/domain/organization.ts","previous-rev":"19f2d2aefc","commit-title":"[AC-2647] Remove Flexible Collections MVP code (#9518)","language":"TypeScript","id":"9625cee0d6bc9023b5de4cc43e591e0455be55f2","model-score":0.43,"author-id":null,"project-id":26215,"delta-file-score":0.61278176,"diff":"diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts\nindex b68de7ac08..2632a16da0 100644\n--- a/libs/common/src/admin-console/models/domain/organization.ts\n+++ b/libs/common/src/admin-console/models/domain/organization.ts\n@@ -144,12 +144,2 @@ export class Organization {\n \n-  /**\n-   * Whether a user has Manager permissions or greater\n-   *\n-   * @deprecated\n-   * This is deprecated with the introduction of Flexible Collections.\n-   */\n-  get isManager() {\n-    return this.type === OrganizationUserType.Manager || this.isAdmin;\n-  }\n-\n   /**\n@@ -181,11 +171,5 @@ export class Organization {\n   get canCreateNewCollections() {\n-    if (this.flexibleCollections) {\n-      return (\n-        !this.limitCollectionCreationDeletion ||\n-        this.isAdmin ||\n-        this.permissions.createNewCollections\n-      );\n-    }\n-\n-    return this.isManager || this.permissions.createNewCollections;\n+    return (\n+      !this.limitCollectionCreationDeletion || this.isAdmin || this.permissions.createNewCollections\n+    );\n   }\n@@ -193,3 +177,3 @@ export class Organization {\n   canEditAnyCollection(flexibleCollectionsV1Enabled: boolean) {\n-    if (!this.flexibleCollections || !flexibleCollectionsV1Enabled) {\n+    if (!flexibleCollectionsV1Enabled) {\n       // Pre-Flexible Collections v1 logic\n@@ -223,4 +207,4 @@ export class Organization {\n   ) {\n-    // Before Flexible Collections, any admin or anyone with editAnyCollection permission could edit all ciphers\n-    if (!this.flexibleCollections || !flexibleCollectionsV1Enabled || !this.flexibleCollections) {\n+    // Before Flexible Collections V1, any admin or anyone with editAnyCollection permission could edit all ciphers\n+    if (!flexibleCollectionsV1Enabled) {\n       return this.isAdmin || this.permissions.editAnyCollection;\n@@ -271,29 +255,2 @@ export class Organization {\n \n-  /**\n-   * @deprecated\n-   * This is deprecated with the introduction of Flexible Collections.\n-   * This will always return false if FlexibleCollections flag is on.\n-   */\n-  get canEditAssignedCollections() {\n-    return this.isManager || this.permissions.editAssignedCollections;\n-  }\n-\n-  /**\n-   * @deprecated\n-   * This is deprecated with the introduction of Flexible Collections.\n-   * This will always return false if FlexibleCollections flag is on.\n-   */\n-  get canDeleteAssignedCollections() {\n-    return this.isManager || this.permissions.deleteAssignedCollections;\n-  }\n-\n-  /**\n-   * @deprecated\n-   * This is deprecated with the introduction of Flexible Collections.\n-   * This will always return false if FlexibleCollections flag is on.\n-   */\n-  get canViewAssignedCollections() {\n-    return this.canDeleteAssignedCollections || this.canEditAssignedCollections;\n-  }\n-\n   get canManageGroups() {\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Andreas Coroiu","training-data":{"loc-added":"18","loc-deleted":"57","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"acoroiu@bitwarden.com","commit-full-message":"* wip\n\n* feat: add dynamic states\n\n* feat: re-implement badge service with dynamic state functions\n\n* feat: completely remove old static states\n\n* feat: debounce calls to badge api per tab\n\n* feat: use group-by to avoid re-setting all tabs on 1 tab change\n\n* feat: simplify autofill badge updater\n\n* feat: add hanging function test\n\n* chore: clean up badge service\n\n* feat: simplify private updateBadge\n\n* feat: remove unnecessary Set usage\n\n* fix: tests that broke after setState rename\n\n* chore: clean up badge api","commit-date":"2025-10-03T07:01:49Z","current-rev":"2ddf1c34b2","filename":"clients/apps/browser/src/autofill/services/autofill-badge-updater.service.ts","previous-rev":"fdf47ffe3b","commit-title":"[PM-25488] Badge stays after lock when using pin (#16436)","language":"TypeScript","id":"f022b9a7d8508802c62cce0e61055d10b3e1bf86","model-score":0.42,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/browser/src/autofill/services/autofill-badge-updater.service.ts b/apps/browser/src/autofill/services/autofill-badge-updater.service.ts\nindex 06ddf16c8a..382c9efa7f 100644\n--- a/apps/browser/src/autofill/services/autofill-badge-updater.service.ts\n+++ b/apps/browser/src/autofill/services/autofill-badge-updater.service.ts\n@@ -1,2 +1,2 @@\n-import { combineLatest, distinctUntilChanged, mergeMap, of, switchMap, withLatestFrom } from \"rxjs\";\n+import { combineLatest, delay, distinctUntilChanged, mergeMap, of, switchMap } from \"rxjs\";\n \n@@ -12,3 +12,3 @@ import { BadgeStatePriority } from \"../../platform/badge/priority\";\n \n-const StateName = (tabId: number) => `autofill-badge-${tabId}`;\n+const StateName = \"autofill-badge-updater\";\n \n@@ -28,52 +28,26 @@ export class AutofillBadgeUpdaterService {\n \n-    // Recalculate badges for all active tabs when ciphers or active account changes\n-    combineLatest({\n-      account: this.accountService.activeAccount$,\n-      enableBadgeCounter:\n-        this.badgeSettingsService.enableBadgeCounter$.pipe(distinctUntilChanged()),\n-      ciphers: ciphers$,\n-    })\n-      .pipe(\n+    this.badgeService.setState(StateName, (tab) => {\n+      return combineLatest({\n+        account: this.accountService.activeAccount$,\n+        enableBadgeCounter:\n+          this.badgeSettingsService.enableBadgeCounter$.pipe(distinctUntilChanged()),\n+        ciphers: ciphers$.pipe(delay(100)), // Delay to allow cipherService.getAllDecryptedForUrl to pick up changes\n+      }).pipe(\n         mergeMap(async ({ account, enableBadgeCounter }) => {\n-          if (!account) {\n-            return;\n-          }\n-\n-          const tabs = await this.badgeService.getActiveTabs();\n-\n-          for (const tab of tabs) {\n-            if (!tab.tabId) {\n-              continue;\n-            }\n-            if (enableBadgeCounter) {\n-              await this.setTabState(tab, account.id);\n-            } else {\n-              await this.clearTabState(tab.tabId);\n-            }\n-          }\n-        }),\n-      )\n-      .subscribe();\n-\n-    // Recalculate badge for a specific tab when it becomes active\n-    this.badgeService.activeTabsUpdated$\n-      .pipe(\n-        withLatestFrom(\n-          this.accountService.activeAccount$,\n-          this.badgeSettingsService.enableBadgeCounter$,\n-        ),\n-        mergeMap(async ([tabs, account, enableBadgeCounter]) => {\n           if (!account || !enableBadgeCounter) {\n-            return;\n+            return undefined;\n           }\n \n-          for (const tab of tabs) {\n-            await this.setTabState(tab, account.id);\n-          }\n+          return {\n+            state: {\n+              text: await this.calculateCountText(tab, account.id),\n+            },\n+            priority: BadgeStatePriority.Default,\n+          };\n         }),\n-      )\n-      .subscribe();\n+      );\n+    });\n   }\n \n-  private async setTabState(tab: Tab, userId: UserId) {\n+  private async calculateCountText(tab: Tab, userId: UserId) {\n     if (!tab.tabId) {\n@@ -87,19 +61,6 @@ export class AutofillBadgeUpdaterService {\n     if (cipherCount === 0) {\n-      await this.clearTabState(tab.tabId);\n-      return;\n+      return undefined;\n     }\n \n-    const countText = cipherCount > 9 ? \"9+\" : cipherCount.toString();\n-    await this.badgeService.setState(\n-      StateName(tab.tabId),\n-      BadgeStatePriority.Default,\n-      {\n-        text: countText,\n-      },\n-      tab.tabId,\n-    );\n-  }\n-\n-  private async clearTabState(tabId: number) {\n-    await this.badgeService.clearState(StateName(tabId));\n+    return cipherCount > 9 ? \"9+\" : cipherCount.toString();\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Cesar Gonzalez","training-data":{"loc-added":"1","loc-deleted":"37","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"cesar.a.gonzalezcs@gmail.com","commit-full-message":"","commit-date":"2024-02-23T22:01:41Z","current-rev":"c6b0f70a26","filename":"clients/apps/browser/src/autofill/background/overlay.background.ts","previous-rev":"ae1b6e84be","commit-title":"[PM-6078] Remove Username Masking From Inline Autofill Menu (#7951)","language":"TypeScript","id":"2d51c56319c4e3b495d93d027e2638d051f9a749","model-score":0.42,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts\nindex e2d4b72c33..c2eb12041a 100644\n--- a/apps/browser/src/autofill/background/overlay.background.ts\n+++ b/apps/browser/src/autofill/background/overlay.background.ts\n@@ -181,6 +181,3 @@ class OverlayBackground implements OverlayBackgroundInterface {\n             : buildCipherIcon(this.iconsServerUrl, cipher, isFaviconDisabled),\n-        login:\n-          cipher.type === CipherType.Login\n-            ? { username: this.obscureName(cipher.login.username) }\n-            : null,\n+        login: cipher.type === CipherType.Login ? { username: cipher.login.username } : null,\n         card: cipher.type === CipherType.Card ? cipher.card.subTitle : null,\n@@ -428,35 +425,2 @@ class OverlayBackground implements OverlayBackgroundInterface {\n \n-  /**\n-   * Obscures the username by replacing all but the first and last characters with asterisks.\n-   * If the username is less than 4 characters, only the first character will be shown.\n-   * If the username is 6 or more characters, the first and last characters will be shown.\n-   * The domain will not be obscured.\n-   *\n-   * @param name - The username to obscure\n-   */\n-  private obscureName(name: string): string {\n-    if (!name) {\n-      return \"\";\n-    }\n-\n-    const [username, domain] = name.split(\"@\");\n-    const usernameLength = username?.length;\n-    if (!usernameLength) {\n-      return name;\n-    }\n-\n-    const startingCharacters = username.slice(0, usernameLength > 4 ? 2 : 1);\n-    let numberStars = usernameLength;\n-    if (usernameLength > 4) {\n-      numberStars = usernameLength < 6 ? numberStars - 1 : numberStars - 2;\n-    }\n-\n-    let obscureName = `${startingCharacters}${new Array(numberStars).join(\"*\")}`;\n-    if (usernameLength >= 6) {\n-      obscureName = `${obscureName}${username.slice(-1)}`;\n-    }\n-\n-    return domain ? `${obscureName}@${domain}` : obscureName;\n-  }\n-\n   /**\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Bernd Schoolmann","training-data":{"loc-added":"1","loc-deleted":"98","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mail@quexten.com","commit-full-message":"","commit-date":"2024-10-23T17:05:24Z","current-rev":"74dabb97bf","filename":"clients/libs/common/src/platform/services/system.service.ts","previous-rev":"eff9a423da","commit-title":"Move process reload ownership to key-management (#10853)","language":"TypeScript","id":"950e1a3dda7f03b76878b5a6f28027889fb159b1","model-score":0.4,"author-id":null,"project-id":26215,"delta-file-score":1.044829,"diff":"diff --git a/libs/common/src/platform/services/system.service.ts b/libs/common/src/platform/services/system.service.ts\nindex 357737391c..03e96af75b 100644\n--- a/libs/common/src/platform/services/system.service.ts\n+++ b/libs/common/src/platform/services/system.service.ts\n@@ -1,14 +1,4 @@\n-import { firstValueFrom, map, Subscription, timeout } from \"rxjs\";\n+import { firstValueFrom, Subscription } from \"rxjs\";\n \n-import { BiometricStateService } from \"@bitwarden/key-management\";\n-\n-import { PinServiceAbstraction } from \"../../../../auth/src/common/abstractions\";\n-import { VaultTimeoutSettingsService } from \"../../abstractions/vault-timeout/vault-timeout-settings.service\";\n-import { AccountService } from \"../../auth/abstractions/account.service\";\n-import { AuthService } from \"../../auth/abstractions/auth.service\";\n-import { AuthenticationStatus } from \"../../auth/enums/authentication-status\";\n import { AutofillSettingsServiceAbstraction } from \"../../autofill/services/autofill-settings.service\";\n-import { VaultTimeoutAction } from \"../../enums/vault-timeout-action.enum\";\n-import { UserId } from \"../../types/guid\";\n-import { MessagingService } from \"../abstractions/messaging.service\";\n import { PlatformUtilsService } from \"../abstractions/platform-utils.service\";\n@@ -20,3 +10,2 @@ import { TaskSchedulerService } from \"../scheduling/task-scheduler.service\";\n export class SystemService implements SystemServiceAbstraction {\n-  private reloadInterval: any = null;\n   private clearClipboardTimeoutSubscription: Subscription;\n@@ -25,10 +14,4 @@ export class SystemService implements SystemServiceAbstraction {\n   constructor(\n-    private pinService: PinServiceAbstraction,\n-    private messagingService: MessagingService,\n     private platformUtilsService: PlatformUtilsService,\n-    private reloadCallback: () => Promise<void> = null,\n     private autofillSettingsService: AutofillSettingsServiceAbstraction,\n-    private vaultTimeoutSettingsService: VaultTimeoutSettingsService,\n-    private biometricStateService: BiometricStateService,\n-    private accountService: AccountService,\n     private taskSchedulerService: TaskSchedulerService,\n@@ -41,82 +24,2 @@ export class SystemService implements SystemServiceAbstraction {\n \n-  async startProcessReload(authService: AuthService): Promise<void> {\n-    const accounts = await firstValueFrom(this.accountService.accounts$);\n-    if (accounts != null) {\n-      const keys = Object.keys(accounts);\n-      if (keys.length > 0) {\n-        for (const userId of keys) {\n-          let status = await firstValueFrom(authService.authStatusFor$(userId as UserId));\n-          status = await authService.getAuthStatus(userId);\n-          if (status === AuthenticationStatus.Unlocked) {\n-            return;\n-          }\n-        }\n-      }\n-    }\n-\n-    // A reloadInterval has already been set and is executing\n-    if (this.reloadInterval != null) {\n-      return;\n-    }\n-\n-    // If there is an active user, check if they have a pinKeyEncryptedUserKeyEphemeral. If so, prevent process reload upon lock.\n-    const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;\n-    if (userId != null) {\n-      const ephemeralPin = await this.pinService.getPinKeyEncryptedUserKeyEphemeral(userId);\n-      if (ephemeralPin != null) {\n-        return;\n-      }\n-    }\n-\n-    this.cancelProcessReload();\n-    await this.executeProcessReload();\n-  }\n-\n-  private async executeProcessReload() {\n-    const biometricLockedFingerprintValidated = await firstValueFrom(\n-      this.biometricStateService.fingerprintValidated$,\n-    );\n-    if (!biometricLockedFingerprintValidated) {\n-      clearInterval(this.reloadInterval);\n-      this.reloadInterval = null;\n-\n-      const activeUserId = await firstValueFrom(\n-        this.accountService.activeAccount$.pipe(\n-          map((a) => a?.id),\n-          timeout(500),\n-        ),\n-      );\n-      // Replace current active user if they will be logged out on reload\n-      if (activeUserId != null) {\n-        const timeoutAction = await firstValueFrom(\n-          this.vaultTimeoutSettingsService\n-            .getVaultTimeoutActionByUserId$(activeUserId)\n-            .pipe(timeout(500)), // safety feature to avoid this call hanging and stopping process reload from clearing memory\n-        );\n-        if (timeoutAction === VaultTimeoutAction.LogOut) {\n-          const nextUser = await firstValueFrom(\n-            this.accountService.nextUpAccount$.pipe(map((account) => account?.id ?? null)),\n-          );\n-          await this.accountService.switchAccount(nextUser);\n-        }\n-      }\n-\n-      this.messagingService.send(\"reloadProcess\");\n-      if (this.reloadCallback != null) {\n-        await this.reloadCallback();\n-      }\n-      return;\n-    }\n-    if (this.reloadInterval == null) {\n-      this.reloadInterval = setInterval(async () => await this.executeProcessReload(), 1000);\n-    }\n-  }\n-\n-  cancelProcessReload(): void {\n-    if (this.reloadInterval != null) {\n-      clearInterval(this.reloadInterval);\n-      this.reloadInterval = null;\n-    }\n-  }\n-\n   async clearClipboard(clipboardValue: string, timeoutMs: number = null): Promise<void> {\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Bernd Schoolmann","training-data":{"loc-added":"87","loc-deleted":"20","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.17","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mail@quexten.com","commit-full-message":"* Remove AES128CBC-HMAC encryption\n\n* Increase test coverage\n\n* Refactor symmetric keys and increase test coverage\n\n* Re-add type 0 encryption\n\n* Fix ts strict warning\n\n* Re-add support for encrypt hmac-less aes\n\n* Add comment about inner()\n\n* Update comment\n\n* Deduplicate encryption type check\n\n* Undo test changes\n\n* Lift out encryption type check to before splitting by encryption type\n\n* Change null to undefined\n\n* Fix test","commit-date":"2025-04-08T10:42:42Z","current-rev":"cf0e693caa","filename":"clients/libs/common/src/platform/models/domain/symmetric-crypto-key.ts","previous-rev":"81350a2ee1","commit-title":"[PM-18697] Add new symmetric key runtime representation and move encrypt service to it (#13578)","language":"TypeScript","id":"eb215feac3694dda370afdc761d2af76fbae3f73","model-score":0.39,"author-id":null,"project-id":26215,"delta-file-score":0.36371157,"diff":"diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts\nindex 372b869fd9..45e15c1f60 100644\n--- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts\n+++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts\n@@ -7,3 +7,21 @@ import { EncryptionType } from \"../../enums\";\n \n+export type Aes256CbcHmacKey = {\n+  type: EncryptionType.AesCbc256_HmacSha256_B64;\n+  encryptionKey: Uint8Array;\n+  authenticationKey: Uint8Array;\n+};\n+\n+export type Aes256CbcKey = {\n+  type: EncryptionType.AesCbc256_B64;\n+  encryptionKey: Uint8Array;\n+};\n+\n+/**\n+ *  A symmetric crypto key represents a symmetric key usable for symmetric encryption and decryption operations.\n+ *  The specific algorithm used is private to the key, and should only be exposed to encrypt service implementations.\n+ *  This can be done via `inner()`.\n+ */\n export class SymmetricCryptoKey {\n+  private innerKey: Aes256CbcHmacKey | Aes256CbcKey;\n+\n   key: Uint8Array;\n@@ -19,3 +37,6 @@ export class SymmetricCryptoKey {\n \n-  constructor(key: Uint8Array, encType?: EncryptionType) {\n+  /**\n+   * @param key The key in one of the permitted serialization formats\n+   */\n+  constructor(key: Uint8Array) {\n     if (key == null) {\n@@ -24,29 +45,33 @@ export class SymmetricCryptoKey {\n \n-    if (encType == null) {\n-      if (key.byteLength === 32) {\n-        encType = EncryptionType.AesCbc256_B64;\n-      } else if (key.byteLength === 64) {\n-        encType = EncryptionType.AesCbc256_HmacSha256_B64;\n-      } else {\n-        throw new Error(\"Unable to determine encType.\");\n-      }\n-    }\n-\n-    this.key = key;\n-    this.encType = encType;\n+    if (key.byteLength === 32) {\n+      this.innerKey = {\n+        type: EncryptionType.AesCbc256_B64,\n+        encryptionKey: key,\n+      };\n+      this.encType = EncryptionType.AesCbc256_B64;\n+      this.key = key;\n+      this.keyB64 = Utils.fromBufferToB64(this.key);\n \n-    if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) {\n       this.encKey = key;\n+      this.encKeyB64 = Utils.fromBufferToB64(this.encKey);\n+\n       this.macKey = null;\n-    } else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && key.byteLength === 64) {\n+      this.macKeyB64 = undefined;\n+    } else if (key.byteLength === 64) {\n+      this.innerKey = {\n+        type: EncryptionType.AesCbc256_HmacSha256_B64,\n+        encryptionKey: key.slice(0, 32),\n+        authenticationKey: key.slice(32),\n+      };\n+      this.encType = EncryptionType.AesCbc256_HmacSha256_B64;\n+      this.key = key;\n+      this.keyB64 = Utils.fromBufferToB64(this.key);\n+\n       this.encKey = key.slice(0, 32);\n-      this.macKey = key.slice(32, 64);\n-    } else {\n-      throw new Error(\"Unsupported encType/key length.\");\n-    }\n+      this.encKeyB64 = Utils.fromBufferToB64(this.encKey);\n \n-    this.keyB64 = Utils.fromBufferToB64(this.key);\n-    this.encKeyB64 = Utils.fromBufferToB64(this.encKey);\n-    if (this.macKey != null) {\n+      this.macKey = key.slice(32);\n       this.macKeyB64 = Utils.fromBufferToB64(this.macKey);\n+    } else {\n+      throw new Error(`Unsupported encType/key length ${key.byteLength}`);\n     }\n@@ -59,2 +84,44 @@ export class SymmetricCryptoKey {\n \n+  /**\n+   * It is preferred not to work with the raw key where possible.\n+   * Only use this method if absolutely necessary.\n+   *\n+   * @returns The inner key instance that can be directly used for encryption primitives\n+   */\n+  inner(): Aes256CbcHmacKey | Aes256CbcKey {\n+    return this.innerKey;\n+  }\n+\n+  /**\n+   * @returns The serialized key in base64 format\n+   */\n+  toBase64(): string {\n+    return Utils.fromBufferToB64(this.toEncoded());\n+  }\n+\n+  /**\n+   * Serializes the key to a format that can be written to state or shared\n+   * The currently permitted format is:\n+   * - AesCbc256_B64: 32 bytes (the raw key)\n+   * - AesCbc256_HmacSha256_B64: 64 bytes (32 bytes encryption key, 32 bytes authentication key, concatenated)\n+   *\n+   * @returns The serialized key that can be written to state or encrypted and then written to state / shared\n+   */\n+  toEncoded(): Uint8Array {\n+    if (this.innerKey.type === EncryptionType.AesCbc256_B64) {\n+      return this.innerKey.encryptionKey;\n+    } else if (this.innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {\n+      const encodedKey = new Uint8Array(64);\n+      encodedKey.set(this.innerKey.encryptionKey, 0);\n+      encodedKey.set(this.innerKey.authenticationKey, 32);\n+      return encodedKey;\n+    } else {\n+      throw new Error(\"Unsupported encryption type.\");\n+    }\n+  }\n+\n+  /**\n+   * @param s The serialized key in base64 format\n+   * @returns A SymmetricCryptoKey instance\n+   */\n   static fromString(s: string): SymmetricCryptoKey {\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Stephon Brown","training-data":{"loc-added":"56","loc-deleted":"15","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"sbrown@livefront.com","commit-full-message":"* feat(billing): Refactor DisplayPaymentMethodInlineComponent for external form control\n\n* feat(billing): Integrate external payment method management in PremiumOrgUpgradePayment\nCleanup: Remove debug console.warn in invoice preview refresh\n\n* test(billing): Update PremiumOrgUpgradePaymentComponent tests\n\n* refactor:add non-null assertion for payment method validation\n\n* refactor: use string selectors for ViewChild\n\n* refactor: remove unused `tap` operator\n\n* test: improve component mocking setup\n\n* feat: add payment method validation on upgrade\n\n* refactor(billing): remove unused updatePaymentInParent input","commit-date":"2026-02-24T20:07:43Z","current-rev":"f667507512","filename":"clients/apps/web/src/app/billing/payment/components/display-payment-method-inline.component.ts","previous-rev":"181e4767d8","commit-title":"[PM-32028] Remove Save and Cancel Buttons (#18954)","language":"TypeScript","id":"341cd6a6c622d4b2794ce354c3455b74fa5f9dd8","model-score":0.39,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/web/src/app/billing/payment/components/display-payment-method-inline.component.ts b/apps/web/src/app/billing/payment/components/display-payment-method-inline.component.ts\nindex aa6d15401f..06a6508c6a 100644\n--- a/apps/web/src/app/billing/payment/components/display-payment-method-inline.component.ts\n+++ b/apps/web/src/app/billing/payment/components/display-payment-method-inline.component.ts\n@@ -10,2 +10,3 @@ import {\n } from \"@angular/core\";\n+import { FormGroup } from \"@angular/forms\";\n \n@@ -92,3 +93,3 @@ import { EnterPaymentMethodComponent } from \"./enter-payment-method.component\";\n           #enterPaymentMethodComponent\n-          [includeBillingAddress]=\"true\"\n+          [includeBillingAddress]=\"false\"\n           [group]=\"formGroup\"\n@@ -98,16 +99,18 @@ import { EnterPaymentMethodComponent } from \"./enter-payment-method.component\";\n         </app-enter-payment-method>\n-        <div class=\"tw-mt-4 tw-flex tw-gap-2\">\n-          <button\n-            bitLink\n-            linkType=\"default\"\n-            type=\"button\"\n-            (click)=\"submit()\"\n-            [disabled]=\"formGroup.invalid\"\n-          >\n-            {{ \"save\" | i18n }}\n-          </button>\n-          <button bitLink linkType=\"subtle\" type=\"button\" (click)=\"cancel()\">\n-            {{ \"cancel\" | i18n }}\n-          </button>\n-        </div>\n+        @if (showFormButtons()) {\n+          <div class=\"tw-mt-4 tw-flex tw-gap-2\">\n+            <button\n+              bitLink\n+              linkType=\"default\"\n+              type=\"button\"\n+              (click)=\"submit()\"\n+              [disabled]=\"formGroup.invalid\"\n+            >\n+              {{ \"save\" | i18n }}\n+            </button>\n+            <button bitLink linkType=\"subtle\" type=\"button\" (click)=\"cancel()\">\n+              {{ \"cancel\" | i18n }}\n+            </button>\n+          </div>\n+        }\n       }\n@@ -122,6 +125,7 @@ export class DisplayPaymentMethodInlineComponent {\n   readonly paymentMethod = input.required<MaskedPaymentMethod | null>();\n+  readonly externalFormGroup = input<FormGroup | null>(null);\n+\n   readonly updated = output<MaskedPaymentMethod>();\n-  readonly changingStateChanged = output<boolean>();\n \n-  protected formGroup = EnterPaymentMethodComponent.getFormGroup();\n+  protected formGroup: FormGroup;\n \n@@ -131,5 +135,9 @@ export class DisplayPaymentMethodInlineComponent {\n \n-  protected readonly isChangingPayment = signal(false);\n+  readonly isChangingPayment = signal(false);\n+\n   protected readonly cardBrandIcon = computed(() => getCardBrandIcon(this.paymentMethod()));\n \n+  // Show submit buttons only when component is managing its own form (no external form provided)\n+  protected readonly showFormButtons = computed(() => this.externalFormGroup() === null);\n+\n   private readonly billingClient = inject(SubscriberBillingClient);\n@@ -139,2 +147,7 @@ export class DisplayPaymentMethodInlineComponent {\n \n+  constructor() {\n+    // Use external form group if provided, otherwise create our own\n+    this.formGroup = this.externalFormGroup() ?? EnterPaymentMethodComponent.getFormGroup();\n+  }\n+\n   /**\n@@ -144,5 +157,50 @@ export class DisplayPaymentMethodInlineComponent {\n     this.isChangingPayment.set(true);\n-    this.changingStateChanged.emit(true);\n   };\n \n+  /**\n+   * Public method to get tokenized payment method data.\n+   * Use this when parent component handles submission.\n+   * Parent is responsible for handling billing address separately.\n+   * @returns Promise with tokenized payment method\n+   */\n+  async getTokenizedPaymentMethod(): Promise<any> {\n+    if (!this.formGroup.valid) {\n+      this.formGroup.markAllAsTouched();\n+      throw new Error(\"Form is invalid\");\n+    }\n+\n+    const component = this.enterPaymentMethodComponent();\n+    if (!component) {\n+      throw new Error(\"Payment method component not found\");\n+    }\n+\n+    const paymentMethod = await component.tokenize();\n+    if (!paymentMethod) {\n+      throw new Error(\"Failed to tokenize payment method\");\n+    }\n+\n+    return paymentMethod;\n+  }\n+\n+  /**\n+   * Validates the form and returns whether it's ready for submission.\n+   * Used when parent component handles submission to determine button state.\n+   */\n+  isFormValid(): boolean {\n+    const enterPaymentMethodComponent = this.enterPaymentMethodComponent();\n+    if (enterPaymentMethodComponent) {\n+      return this.enterPaymentMethodComponent()!.validate();\n+    }\n+    return false;\n+  }\n+\n+  /**\n+   * Public method to reset the form and exit edit mode.\n+   * Use this after parent successfully handles the update.\n+   */\n+  resetForm(): void {\n+    this.formGroup.reset();\n+    this.isChangingPayment.set(false);\n+  }\n+\n   /**\n@@ -153,16 +211,3 @@ export class DisplayPaymentMethodInlineComponent {\n     try {\n-      if (!this.formGroup.valid) {\n-        this.formGroup.markAllAsTouched();\n-        throw new Error(\"Form is invalid\");\n-      }\n-\n-      const component = this.enterPaymentMethodComponent();\n-      if (!component) {\n-        throw new Error(\"Payment method component not found\");\n-      }\n-\n-      const paymentMethod = await component.tokenize();\n-      if (!paymentMethod) {\n-        throw new Error(\"Failed to tokenize payment method\");\n-      }\n+      const paymentMethod = await this.getTokenizedPaymentMethod();\n \n@@ -203,5 +248,3 @@ export class DisplayPaymentMethodInlineComponent {\n         this.updated.emit(result.value);\n-        this.isChangingPayment.set(false);\n-        this.changingStateChanged.emit(false);\n-        this.formGroup.reset();\n+        this.resetForm();\n         break;\n@@ -225,5 +268,3 @@ export class DisplayPaymentMethodInlineComponent {\n   protected cancel = (): void => {\n-    this.formGroup.reset();\n-    this.changingStateChanged.emit(false);\n-    this.isChangingPayment.set(false);\n+    this.resetForm();\n   };\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Jordan Aasen","training-data":{"loc-added":"38","loc-deleted":"39","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.26","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"166539328+jaasen-livefront@users.noreply.github.com","commit-full-message":"","commit-date":"2026-04-03T18:29:02Z","current-rev":"d22a5a82c9","filename":"clients/libs/common/src/models/export/fido2-credential.export.ts","previous-rev":"1a1c5f4df2","commit-title":"migrate cipher export and sub-models (#19050)","language":"TypeScript","id":"818a6dcd368b606ffb2b877b0a0759610b016e00","model-score":0.37,"author-id":null,"project-id":26215,"delta-file-score":0.39107287,"diff":"diff --git a/libs/common/src/models/export/fido2-credential.export.ts b/libs/common/src/models/export/fido2-credential.export.ts\nindex 46131a6706..af614a79e9 100644\n--- a/libs/common/src/models/export/fido2-credential.export.ts\n+++ b/libs/common/src/models/export/fido2-credential.export.ts\n@@ -1,3 +1 @@\n-// FIXME: Update this file to be type safe and remove this and next line\n-// @ts-strict-ignore\n import { EncString } from \"../../key-management/crypto/models/enc-string\";\n@@ -30,3 +28,3 @@ export class Fido2CredentialExport {\n     req.discoverable = \"false\";\n-    req.creationDate = null;\n+    req.creationDate = new Date();\n     return req;\n@@ -64,16 +62,16 @@ export class Fido2CredentialExport {\n   static toDomain(req: Fido2CredentialExport, domain = new Fido2Credential()) {\n-    domain.credentialId = req.credentialId != null ? new EncString(req.credentialId) : null;\n-    domain.keyType = req.keyType != null ? new EncString(req.keyType) : null;\n-    domain.keyAlgorithm = req.keyAlgorithm != null ? new EncString(req.keyAlgorithm) : null;\n-    domain.keyCurve = req.keyCurve != null ? new EncString(req.keyCurve) : null;\n-    domain.keyValue = req.keyValue != null ? new EncString(req.keyValue) : null;\n-    domain.rpId = req.rpId != null ? new EncString(req.rpId) : null;\n-    domain.userHandle = req.userHandle != null ? new EncString(req.userHandle) : null;\n-    domain.userName = req.userName != null ? new EncString(req.userName) : null;\n-    domain.counter = req.counter != null ? new EncString(req.counter) : null;\n-    domain.rpName = req.rpName != null ? new EncString(req.rpName) : null;\n+    domain.credentialId = new EncString(req.credentialId);\n+    domain.keyType = new EncString(req.keyType);\n+    domain.keyAlgorithm = new EncString(req.keyAlgorithm);\n+    domain.keyCurve = new EncString(req.keyCurve);\n+    domain.keyValue = new EncString(req.keyValue);\n+    domain.rpId = new EncString(req.rpId);\n+    domain.userHandle = req.userHandle != null ? new EncString(req.userHandle) : undefined;\n+    domain.userName = req.userName != null ? new EncString(req.userName) : undefined;\n+    domain.counter = new EncString(req.counter);\n+    domain.rpName = req.rpName != null ? new EncString(req.rpName) : undefined;\n     domain.userDisplayName =\n-      req.userDisplayName != null ? new EncString(req.userDisplayName) : null;\n-    domain.discoverable = req.discoverable != null ? new EncString(req.discoverable) : null;\n-    domain.creationDate = req.creationDate != null ? new Date(req.creationDate) : null;\n+      req.userDisplayName != null ? new EncString(req.userDisplayName) : undefined;\n+    domain.discoverable = new EncString(req.discoverable);\n+    domain.creationDate = new Date(req.creationDate);\n     return domain;\n@@ -81,15 +79,15 @@ export class Fido2CredentialExport {\n \n-  credentialId: string;\n-  keyType: string;\n-  keyAlgorithm: string;\n-  keyCurve: string;\n-  keyValue: string;\n-  rpId: string;\n-  userHandle: string;\n-  userName: string;\n-  counter: string;\n-  rpName: string;\n-  userDisplayName: string;\n-  discoverable: string;\n-  creationDate: Date;\n+  credentialId: string = \"\";\n+  keyType: string = \"\";\n+  keyAlgorithm: string = \"\";\n+  keyCurve: string = \"\";\n+  keyValue: string = \"\";\n+  rpId: string = \"\";\n+  userHandle?: string;\n+  userName?: string;\n+  counter: string = \"\";\n+  rpName?: string;\n+  userDisplayName?: string;\n+  discoverable: string = \"false\";\n+  creationDate: Date = new Date();\n \n@@ -105,16 +103,17 @@ export class Fido2CredentialExport {\n \n-    this.credentialId = safeGetString(o.credentialId);\n-    this.keyType = safeGetString(o.keyType);\n-    this.keyAlgorithm = safeGetString(o.keyAlgorithm);\n-    this.keyCurve = safeGetString(o.keyCurve);\n-    this.keyValue = safeGetString(o.keyValue);\n-    this.rpId = safeGetString(o.rpId);\n+    this.credentialId = safeGetString(o.credentialId) ?? \"\";\n+    this.keyType = safeGetString(o.keyType) ?? \"\";\n+    this.keyAlgorithm = safeGetString(o.keyAlgorithm) ?? \"\";\n+    this.keyCurve = safeGetString(o.keyCurve) ?? \"\";\n+    this.keyValue = safeGetString(o.keyValue) ?? \"\";\n+    this.rpId = safeGetString(o.rpId) ?? \"\";\n     this.userHandle = safeGetString(o.userHandle);\n     this.userName = safeGetString(o.userName);\n-    this.counter = safeGetString(o instanceof Fido2CredentialView ? String(o.counter) : o.counter);\n+    this.counter =\n+      safeGetString(o instanceof Fido2CredentialView ? String(o.counter) : o.counter) ?? \"\";\n     this.rpName = safeGetString(o.rpName);\n     this.userDisplayName = safeGetString(o.userDisplayName);\n-    this.discoverable = safeGetString(\n-      o instanceof Fido2CredentialView ? String(o.discoverable) : o.discoverable,\n-    );\n+    this.discoverable =\n+      safeGetString(o instanceof Fido2CredentialView ? String(o.discoverable) : o.discoverable) ??\n+      \"false\";\n     this.creationDate = o.creationDate;\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Stephon Brown","training-data":{"loc-added":"67","loc-deleted":"26","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.26","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"sbrown@livefront.com","commit-full-message":"* refactor(billing): Modernize organization plans template with block syntax\n\n* Refactor: Update `@for` loop tracking with `$index`\n\n* Feat: Migrate component properties to Angular signals\n\n* Refactor: Adapt component logic for signal consumption and improve structure\n\n* feat(admin-console): Bind OrganizationPlansComponent inputs directly in template\n\n* refactor(admin-console): Remove ViewChild for OrganizationPlansComponent configuration\n\n* refactor(billing): Switch productTier and plan to input signals\n\n* refactor(billing): Update productTier and plan access to use form controls\n\n* refactor(billing): Rename 'isCreatingNewOrganization' getter\n\n* refactor(billing): Access enterPaymentMethodComponent as a signal\n\n* feat(billing): use createOrganization property for organization creation flow\n\n* refactor(billing): migrate organization plans tests to signals and reactive forms\n\n* refactor(billing): introduce setupMockUpgradeOrganization helper in tests\n\n* refactor(billing): update getters to computed signals\n\n* refactor(billing): update template to use new computed signals\n\n* tests(billing): update tests with computed signals\n\n* feat(billing): update display of specific forms when user is upgrading from premium\n\n* tests(billing): add premium upgrade tests\n\n* fix(billing): update type issues for organization plans\n\n* tests(billing): update failing test\n\n* refactor(billing): rename plan/productTier inputs to initial* for clarity\n\n* refactor(billing): update templates to use renamed initial* inputs\n\n* test(billing): update tests to use renamed initial* inputs\n\n* feat(billing): feature flag upgrade from premium changes\n\n* test(billing): update tests for featureflag\n\n* refactor(ui): Improve HTML templating for organization plans\n\n* refactor(component): Safely derive 'hasPremiumPersonally' signal\n\n* refactor(component): Streamline 'createCloudHosted' method and key handling\n\n* feat(billing): Enable organization key submission for premium upgrades\n\n* test(billing): Enhance and adjust organization plan submission tests\n\n* refactor: remove unused imports and types\n\n* refactor(billing): introduce DTO for organization upgrade request\n\n* feat(billing): implement organization key and collection encryption\n\n* refactor(billing): integrate DTO and encryption logic in upgrade service\n\n* test(billing): update specs and components for organization upgrade DTO\n\n* refactor(billing): relocate FamiliesForEnterpriseSetupComponent to billing module\n\n* refactor(admin-console): revert families-for-enterprise component\n\n* refactor: rename wrappedPrivateKey to encryptedPrivateKey\n\n* refactor(billing): rename encrypted private key variable\n\n* feat(billing): add tier ids and types\n\n* refactor(billing): remove unused imports and sync service\n\n* fix(billing): correct tier passing in upgrade function\n\n* feat(billing): add conversion function from product tier to subscription tier id\n\n* refactor(billing): enhance organization encryption data generation\n\n* fix(billing): enable response for account upgrade\n\n* fix(billing): pass plan tier instead of plan details\n\n* refactor(test): remove unnecessary organization service mock\n\n* test: update upgrade service tests\n\n* tests(billing): update tests\n\n* refactor(billing): import account type\nfeat(billing): add organization creation message\n\n* refactor(billing): remove unused imports and constants\n\n* refactor(billing): remove unused viewchild\n\n* fix(billing): remove selectedfile variable\n\n* feat(billing): update organization upgrade logic\n\n* refactor(billing): remove unused upgrade functions\n\n* test: remove unnecessary setupMockEncryptionKeys calls\n\n* refactor: remove unused mockAccountBillingClient\n\n* feat: introduce PremiumOrgUpgradeService\n\n* refactor: simplify form value access and remove accountbilling\n\n* fix(billing): handle type validation\n\n* test(billing): add initial plan and product tier input tests\n\n* feat(billing): set initial plan and product tier values\n\n* refactor(billing): ensure selected plan exists before accessing type","commit-date":"2026-03-02T17:16:29Z","current-rev":"2facb7e04f","filename":"clients/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/services/premium-org-upgrade.service.ts","previous-rev":"52c551c0fe","commit-title":"[PM-29823] New Organization Upgrade Path (#19080)","language":"TypeScript","id":"cc8740476bf06c3cc7b0b5eb630e9006354bfc3f","model-score":0.33,"author-id":null,"project-id":26215,"delta-file-score":0.39107287,"diff":"diff --git a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/services/premium-org-upgrade.service.ts b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/services/premium-org-upgrade.service.ts\nindex 59c97e0373..ed394b5682 100644\n--- a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/services/premium-org-upgrade.service.ts\n+++ b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/services/premium-org-upgrade.service.ts\n@@ -1,5 +1,3 @@\n import { Injectable } from \"@angular/core\";\n-import { firstValueFrom } from \"rxjs\";\n \n-import { OrganizationService } from \"@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction\";\n import { Account } from \"@bitwarden/common/auth/abstractions/account.service\";\n@@ -9,6 +7,12 @@ import {\n   BusinessSubscriptionPricingTierId,\n+  BusinessSubscriptionPricingTierIds,\n   PersonalSubscriptionPricingTier,\n   PersonalSubscriptionPricingTierId,\n+  PersonalSubscriptionPricingTierIds,\n   SubscriptionCadenceIds,\n } from \"@bitwarden/common/billing/types/subscription-pricing-tier\";\n+import { EncryptService } from \"@bitwarden/common/key-management/crypto/abstractions/encrypt.service\";\n+import { EncString } from \"@bitwarden/common/key-management/crypto/models/enc-string\";\n+import { I18nService } from \"@bitwarden/common/platform/abstractions/i18n.service\";\n+import { SymmetricCryptoKey } from \"@bitwarden/common/platform/models/domain/symmetric-crypto-key\";\n import { OrgKey } from \"@bitwarden/common/types/key\";\n@@ -16,2 +20,3 @@ import { SyncService } from \"@bitwarden/common/vault/abstractions/sync/sync.serv\n import { KeyService } from \"@bitwarden/key-management\";\n+import { UserId } from \"@bitwarden/user-core\";\n \n@@ -48,5 +53,6 @@ export class PremiumOrgUpgradeService {\n     private previewInvoiceClient: PreviewInvoiceClient,\n-    private syncService: SyncService,\n     private keyService: KeyService,\n-    private organizationService: OrganizationService,\n+    private i18nService: I18nService,\n+    private encryptService: EncryptService,\n+    private syncService: SyncService,\n   ) {}\n@@ -74,3 +80,3 @@ export class PremiumOrgUpgradeService {\n     organizationName: string,\n-    planDetails: PremiumOrgUpgradePlanDetails,\n+    tier: PersonalSubscriptionPricingTierId | BusinessSubscriptionPricingTierId,\n     billingAddress: BillingAddress,\n@@ -85,16 +91,15 @@ export class PremiumOrgUpgradeService {\n \n-    const tier: ProductTierType = this.ProductTierTypeFromSubscriptionTierId(planDetails.tier);\n-    const [encryptedKey] = await this.keyService.makeOrgKey<OrgKey>(account.id);\n-\n-    if (!encryptedKey.encryptedString) {\n-      throw new Error(\"Failed to generate encrypted organization key\");\n-    }\n+    const productTier: ProductTierType = this.ProductTierTypeFromSubscriptionTierId(tier);\n+    const encryptionData = await this.generateOrganizationEncryptionData(account.id);\n \n-    await this.accountBillingClient.upgradePremiumToOrganization(\n+    const orgId = await this.accountBillingClient.upgradePremiumToOrganization({\n       organizationName,\n-      encryptedKey.encryptedString,\n-      tier,\n-      SubscriptionCadenceIds.Annually,\n+      organizationKey: encryptionData.key,\n+      collectionName: encryptionData.collectionCt,\n+      publicKey: encryptionData.orgKeys[0],\n+      encryptedPrivateKey: encryptionData.orgKeys[1].encryptedString as string,\n+      planTier: productTier,\n+      cadence: SubscriptionCadenceIds.Annually,\n       billingAddress,\n-    );\n+    });\n \n@@ -102,12 +107,3 @@ export class PremiumOrgUpgradeService {\n \n-    // Get the newly created organization\n-    const organizations = await firstValueFrom(this.organizationService.organizations$(account.id));\n-\n-    const newOrg = organizations?.find((org) => org.name === organizationName && org.isOwner);\n-\n-    if (!newOrg) {\n-      throw new Error(\"Failed to find newly created organization\");\n-    }\n-\n-    return newOrg.id;\n+    return orgId;\n   }\n@@ -128,2 +124,47 @@ export class PremiumOrgUpgradeService {\n   }\n+\n+  SubscriptionTierIdFromProductTier(\n+    productTier: ProductTierType,\n+  ): BusinessSubscriptionPricingTierId | PersonalSubscriptionPricingTierId {\n+    switch (productTier) {\n+      case ProductTierType.Families:\n+        return PersonalSubscriptionPricingTierIds.Families;\n+      case ProductTierType.Teams:\n+        return BusinessSubscriptionPricingTierIds.Teams;\n+      case ProductTierType.Enterprise:\n+        return BusinessSubscriptionPricingTierIds.Enterprise;\n+      default:\n+        throw new Error(`Unsupported product tier: ${productTier}`);\n+    }\n+  }\n+\n+  /**\n+   * Generates encryption data needed for creating a new organization.\n+   * Uses the active user account signal to get the user ID.\n+   * @returns Organization encryption data including keys and encrypted collection name\n+   */\n+  async generateOrganizationEncryptionData(activeUserId: UserId): Promise<{\n+    key: string;\n+    collectionCt: string;\n+    orgKeys: [string, EncString];\n+    orgKey: SymmetricCryptoKey;\n+    activeUserId: UserId;\n+  }> {\n+    const orgKey = await this.keyService.makeOrgKey<OrgKey>(activeUserId);\n+    const key = orgKey[0].encryptedString as string;\n+    const collection = await this.encryptService.encryptString(\n+      this.i18nService.t(\"defaultCollection\"),\n+      orgKey[1],\n+    );\n+    const collectionCt = collection.encryptedString as string;\n+    const orgKeys = await this.keyService.makeKeyPair(orgKey[1]);\n+\n+    return {\n+      key,\n+      collectionCt,\n+      orgKeys,\n+      orgKey: orgKey[1],\n+      activeUserId,\n+    };\n+  }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Bernd Schoolmann","training-data":{"loc-added":"7","loc-deleted":"61","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"2.34","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"mail@quexten.com","commit-full-message":"* Implement register SDK service\n\n* Relative import\n\n* Relative import\n\n* Rename to registerClient\n\n* Update libs/common/src/platform/abstractions/sdk/register-sdk.service.ts\n\nCo-authored-by: Derek Nance <dnance@bitwarden.com>\n\n* Rename\n\n---------\n\nCo-authored-by: Derek Nance <dnance@bitwarden.com>","commit-date":"2025-11-26T14:47:20Z","current-rev":"5c7e78a80f","filename":"clients/libs/common/src/platform/services/sdk/default-sdk.service.ts","previous-rev":"72024e71d9","commit-title":"[PM-27835] Implement register SDK service (#17632)","language":"TypeScript","id":"f91e0774afaaca8b0badf761aee90a1497145418","model-score":0.3,"author-id":null,"project-id":26215,"delta-file-score":0.6880334,"diff":"diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts\nindex eb663c6f92..6e7bcbb197 100644\n--- a/libs/common/src/platform/services/sdk/default-sdk.service.ts\n+++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts\n@@ -24,3 +24,2 @@ import {\n   ClientSettings,\n-  DeviceType as SdkDeviceType,\n   TokenProvider,\n@@ -31,3 +30,2 @@ import { ApiService } from \"../../../abstractions/api.service\";\n import { AccountInfo, AccountService } from \"../../../auth/abstractions/account.service\";\n-import { DeviceType } from \"../../../enums/device-type.enum\";\n import { EncryptedString, EncString } from \"../../../key-management/crypto/models/enc-string\";\n@@ -41,3 +39,8 @@ import { SdkClientFactory } from \"../../abstractions/sdk/sdk-client-factory\";\n import { SdkLoadService } from \"../../abstractions/sdk/sdk-load.service\";\n-import { asUuid, SdkService, UserNotLoggedInError } from \"../../abstractions/sdk/sdk.service\";\n+import {\n+  asUuid,\n+  SdkService,\n+  toSdkDevice,\n+  UserNotLoggedInError,\n+} from \"../../abstractions/sdk/sdk.service\";\n import { compareValues } from \"../../misc/compare-values\";\n@@ -299,3 +302,3 @@ export class DefaultSdkService implements SdkService {\n       identityUrl: env.getIdentityUrl(),\n-      deviceType: this.toDevice(this.platformUtilsService.getDevice()),\n+      deviceType: toSdkDevice(this.platformUtilsService.getDevice()),\n       userAgent: this.userAgent ?? navigator.userAgent,\n@@ -303,59 +306,2 @@ export class DefaultSdkService implements SdkService {\n   }\n-\n-  private toDevice(device: DeviceType): SdkDeviceType {\n-    switch (device) {\n-      case DeviceType.Android:\n-        return \"Android\";\n-      case DeviceType.iOS:\n-        return \"iOS\";\n-      case DeviceType.ChromeExtension:\n-        return \"ChromeExtension\";\n-      case DeviceType.FirefoxExtension:\n-        return \"FirefoxExtension\";\n-      case DeviceType.OperaExtension:\n-        return \"OperaExtension\";\n-      case DeviceType.EdgeExtension:\n-        return \"EdgeExtension\";\n-      case DeviceType.WindowsDesktop:\n-        return \"WindowsDesktop\";\n-      case DeviceType.MacOsDesktop:\n-        return \"MacOsDesktop\";\n-      case DeviceType.LinuxDesktop:\n-        return \"LinuxDesktop\";\n-      case DeviceType.ChromeBrowser:\n-        return \"ChromeBrowser\";\n-      case DeviceType.FirefoxBrowser:\n-        return \"FirefoxBrowser\";\n-      case DeviceType.OperaBrowser:\n-        return \"OperaBrowser\";\n-      case DeviceType.EdgeBrowser:\n-        return \"EdgeBrowser\";\n-      case DeviceType.IEBrowser:\n-        return \"IEBrowser\";\n-      case DeviceType.UnknownBrowser:\n-        return \"UnknownBrowser\";\n-      case DeviceType.AndroidAmazon:\n-        return \"AndroidAmazon\";\n-      case DeviceType.UWP:\n-        return \"UWP\";\n-      case DeviceType.SafariBrowser:\n-        return \"SafariBrowser\";\n-      case DeviceType.VivaldiBrowser:\n-        return \"VivaldiBrowser\";\n-      case DeviceType.VivaldiExtension:\n-        return \"VivaldiExtension\";\n-      case DeviceType.SafariExtension:\n-        return \"SafariExtension\";\n-      case DeviceType.Server:\n-        return \"Server\";\n-      case DeviceType.WindowsCLI:\n-        return \"WindowsCLI\";\n-      case DeviceType.MacOsCLI:\n-        return \"MacOsCLI\";\n-      case DeviceType.LinuxCLI:\n-        return \"LinuxCLI\";\n-      default:\n-        return \"SDK\";\n-    }\n-  }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Brandon Treston","training-data":{"loc-added":"4","loc-deleted":"43","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"btreston@bitwarden.com","commit-full-message":"","commit-date":"2026-03-25T18:09:55Z","current-rev":"473929f0d8","filename":"clients/apps/web/src/app/admin-console/organizations/members/members.component.ts","previous-rev":"c7544e40c2","commit-title":"remove feature flagged logic (#19718)","language":"TypeScript","id":"559703b9fa2091d81e9c61f8f3e18aae400c8a79","model-score":0.29,"author-id":null,"project-id":26215,"delta-file-score":0.3009901,"diff":"diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts\nindex 6b93edc8c6..1655765a2c 100644\n--- a/apps/web/src/app/admin-console/organizations/members/members.component.ts\n+++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts\n@@ -35,4 +35,2 @@ import { OrganizationMetadataServiceAbstraction } from \"@bitwarden/common/billin\n import { OrganizationBillingMetadataResponse } from \"@bitwarden/common/billing/models/response/organization-billing-metadata.response\";\n-import { FeatureFlag } from \"@bitwarden/common/enums/feature-flag.enum\";\n-import { ConfigService } from \"@bitwarden/common/platform/abstractions/config/config.service\";\n import { EnvironmentService } from \"@bitwarden/common/platform/abstractions/environment.service\";\n@@ -48,3 +46,2 @@ import { OrganizationWarningsService } from \"@bitwarden/web-vault/app/billing/or\n import {\n-  CloudBulkReinviteLimit,\n   MaxCheckedCount,\n@@ -105,3 +102,2 @@ export class MembersComponent {\n   private memberExportService = inject(MemberExportService);\n-  private configService = inject(ConfigService);\n \n@@ -150,6 +146,2 @@ export class MembersComponent {\n \n-  protected readonly bulkReinviteUIEnabled = toSignal(\n-    this.configService.getFeatureFlag$(FeatureFlag.BulkReinviteUI),\n-  );\n-\n   protected billingMetadata$: Observable<OrganizationBillingMetadataResponse>;\n@@ -402,18 +394,5 @@ export class MembersComponent {\n     const allInvitedUsers = users.filter((u) => u.status === OrganizationUserStatusType.Invited);\n+    const invitedCount = allInvitedUsers.length;\n \n-    // Capture the original count BEFORE enforcing the limit\n-    const originalInvitedCount = allInvitedUsers.length;\n-\n-    // In cloud environments, limit invited users and uncheck the excess\n-    let filteredUsers: OrganizationUserView[];\n-    if (this.dataSource().isIncreasedBulkLimitEnabled() && !this.bulkReinviteUIEnabled()) {\n-      filteredUsers = this.dataSource().limitAndUncheckExcess(\n-        allInvitedUsers,\n-        CloudBulkReinviteLimit,\n-      );\n-    } else {\n-      filteredUsers = allInvitedUsers;\n-    }\n-\n-    if (filteredUsers.length <= 0) {\n+    if (invitedCount <= 0) {\n       this.toastService.showToast({\n@@ -426,3 +405,3 @@ export class MembersComponent {\n \n-    const result = await this.memberActionsService.bulkReinvite(organization, filteredUsers);\n+    const result = await this.memberActionsService.bulkReinvite(organization, allInvitedUsers);\n \n@@ -432,28 +411,10 @@ export class MembersComponent {\n \n-    // In cloud environments, show toast instead of dialog\n     if (this.dataSource().isIncreasedBulkLimitEnabled()) {\n-      const selectedCount = originalInvitedCount;\n-      const invitedCount = filteredUsers.length;\n-\n-      // Only show limited toast if feature flag is disabled and limit was applied\n-      if (!this.bulkReinviteUIEnabled() && selectedCount > CloudBulkReinviteLimit) {\n-        const excludedCount = selectedCount - CloudBulkReinviteLimit;\n-        this.toastService.showToast({\n-          variant: \"success\",\n-          message: this.i18nService.t(\n-            \"bulkReinviteLimitedSuccessToast\",\n-            CloudBulkReinviteLimit.toLocaleString(),\n-            selectedCount.toLocaleString(),\n-            excludedCount.toLocaleString(),\n-          ),\n-        });\n-      } else {\n-        this.toastService.showToast({\n-          variant: \"success\",\n-          message:\n-            invitedCount === 1\n-              ? this.i18nService.t(\"reinviteSuccessToast\")\n-              : this.i18nService.t(\"bulkReinviteSentToast\", invitedCount.toString()),\n-        });\n-      }\n+      this.toastService.showToast({\n+        variant: \"success\",\n+        message:\n+          invitedCount === 1\n+            ? this.i18nService.t(\"reinviteSuccessToast\")\n+            : this.i18nService.t(\"bulkReinviteSentToast\", invitedCount.toString()),\n+      });\n     } else {\n@@ -462,3 +423,3 @@ export class MembersComponent {\n         users,\n-        filteredUsers,\n+        allInvitedUsers,\n         Promise.resolve(result.successful),\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"✨ Audrey ✨","training-data":{"loc-added":"57","loc-deleted":"86","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"2.79","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"ajensen@bitwarden.com","commit-full-message":"* introduce email-randomizer\r\n* introduce email-calculator\r\n* introduce password-randomizer\r\n* introduce username-randomizer\r\n* move randomizer abstraction","commit-date":"2024-07-19T20:34:39Z","current-rev":"e22568f05a","filename":"clients/libs/tools/generator/core/src/strategies/password-generator-strategy.ts","previous-rev":"5b5c165e10","commit-title":"[PM-9422] generator engines (#10032)","language":"TypeScript","id":"c8945e02587c83251feef356e62f7bc348bcde13","model-score":0.28,"author-id":null,"project-id":26215,"delta-file-score":1.398546,"diff":"diff --git a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts\nindex d8e59d3105..587c5609e0 100644\n--- a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts\n+++ b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts\n@@ -3,7 +3,8 @@ import { StateProvider } from \"@bitwarden/common/platform/state\";\n \n-import { GeneratorStrategy, Randomizer } from \"../abstractions\";\n+import { GeneratorStrategy } from \"../abstractions\";\n import { Policies, DefaultPasswordGenerationOptions } from \"../data\";\n+import { PasswordRandomizer } from \"../engine\";\n import { mapPolicyToEvaluator } from \"../rx\";\n import { PasswordGenerationOptions, PasswordGeneratorPolicy } from \"../types\";\n-import { clone$PerUserId, sharedStateByUserId } from \"../util\";\n+import { clone$PerUserId, sharedStateByUserId, sum } from \"../util\";\n \n@@ -19,3 +20,3 @@ export class PasswordGeneratorStrategy\n   constructor(\n-    private randomizer: Randomizer,\n+    private randomizer: PasswordRandomizer,\n     private stateProvider: StateProvider,\n@@ -33,91 +34,61 @@ export class PasswordGeneratorStrategy\n   async generate(options: PasswordGenerationOptions): Promise<string> {\n-    const o = { ...DefaultPasswordGenerationOptions, ...options };\n-    let positions: string[] = [];\n-    if (o.lowercase && o.minLowercase > 0) {\n-      for (let i = 0; i < o.minLowercase; i++) {\n-        positions.push(\"l\");\n-      }\n-    }\n-    if (o.uppercase && o.minUppercase > 0) {\n-      for (let i = 0; i < o.minUppercase; i++) {\n-        positions.push(\"u\");\n-      }\n-    }\n-    if (o.number && o.minNumber > 0) {\n-      for (let i = 0; i < o.minNumber; i++) {\n-        positions.push(\"n\");\n-      }\n-    }\n-    if (o.special && o.minSpecial > 0) {\n-      for (let i = 0; i < o.minSpecial; i++) {\n-        positions.push(\"s\");\n-      }\n-    }\n-    while (positions.length < o.length) {\n-      positions.push(\"a\");\n-    }\n-\n-    // shuffle\n-    positions = await this.randomizer.shuffle(positions);\n-\n-    // build out the char sets\n-    let allCharSet = \"\";\n-\n-    let lowercaseCharSet = \"abcdefghijkmnopqrstuvwxyz\";\n-    if (o.ambiguous) {\n-      lowercaseCharSet += \"l\";\n-    }\n-    if (o.lowercase) {\n-      allCharSet += lowercaseCharSet;\n-    }\n-\n-    let uppercaseCharSet = \"ABCDEFGHJKLMNPQRSTUVWXYZ\";\n-    if (o.ambiguous) {\n-      uppercaseCharSet += \"IO\";\n-    }\n-    if (o.uppercase) {\n-      allCharSet += uppercaseCharSet;\n-    }\n+    // converts password generation option sets, which are defined by\n+    // an \"enabled\" and \"quantity\" parameter, to the password engine's\n+    // parameters, which represent disabled options as `undefined`\n+    // properties.\n+    function process(\n+      // values read from the options\n+      enabled: boolean,\n+      quantity: number,\n+      // value used if an option is missing\n+      defaultEnabled: boolean,\n+      defaultQuantity: number,\n+    ) {\n+      const isEnabled = enabled ?? defaultEnabled;\n+      const actualQuantity = quantity ?? defaultQuantity;\n+      const result = isEnabled ? actualQuantity : undefined;\n \n-    let numberCharSet = \"23456789\";\n-    if (o.ambiguous) {\n-      numberCharSet += \"01\";\n-    }\n-    if (o.number) {\n-      allCharSet += numberCharSet;\n+      return result;\n     }\n \n-    const specialCharSet = \"!@#$%^&*\";\n-    if (o.special) {\n-      allCharSet += specialCharSet;\n-    }\n+    const request = {\n+      uppercase: process(\n+        options.uppercase,\n+        options.minUppercase,\n+        DefaultPasswordGenerationOptions.uppercase,\n+        DefaultPasswordGenerationOptions.minUppercase,\n+      ),\n+      lowercase: process(\n+        options.lowercase,\n+        options.minLowercase,\n+        DefaultPasswordGenerationOptions.lowercase,\n+        DefaultPasswordGenerationOptions.minLowercase,\n+      ),\n+      digits: process(\n+        options.number,\n+        options.minNumber,\n+        DefaultPasswordGenerationOptions.number,\n+        DefaultPasswordGenerationOptions.minNumber,\n+      ),\n+      special: process(\n+        options.special,\n+        options.minSpecial,\n+        DefaultPasswordGenerationOptions.special,\n+        DefaultPasswordGenerationOptions.minSpecial,\n+      ),\n+      ambiguous: options.ambiguous ?? DefaultPasswordGenerationOptions.ambiguous,\n+      all: 0,\n+    };\n \n-    let password = \"\";\n-    for (let i = 0; i < o.length; i++) {\n-      let positionChars: string;\n-      switch (positions[i]) {\n-        case \"l\":\n-          positionChars = lowercaseCharSet;\n-          break;\n-        case \"u\":\n-          positionChars = uppercaseCharSet;\n-          break;\n-        case \"n\":\n-          positionChars = numberCharSet;\n-          break;\n-        case \"s\":\n-          positionChars = specialCharSet;\n-          break;\n-        case \"a\":\n-          positionChars = allCharSet;\n-          break;\n-        default:\n-          break;\n-      }\n+    // engine represents character sets as \"include only\"; you assert how many all\n+    // characters there can be rather than a total length. This conversion has\n+    // the character classes win, so that the result is always consistent with policy\n+    // minimums.\n+    const required = sum(request.uppercase, request.lowercase, request.digits, request.special);\n+    const remaining = (options.length ?? 0) - required;\n+    request.all = Math.max(remaining, 0);\n \n-      const randomCharIndex = await this.randomizer.uniform(0, positionChars.length - 1);\n-      password += positionChars.charAt(randomCharIndex);\n-    }\n+    const result = await this.randomizer.randomAscii(request);\n \n-    return password;\n+    return result;\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vijay Oommen","training-data":{"loc-added":"57","loc-deleted":"49","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"voommen@livefront.com","commit-full-message":"","commit-date":"2025-06-16T21:33:07Z","current-rev":"a9548f519e","filename":"clients/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/services/member-access-report.service.ts","previous-rev":"fcd24a4d60","commit-title":"[PM-20112] Update Member Access report to use new server model (#15155)","language":"TypeScript","id":"e464bfd0dd9115614cc042fe3c4aa56c138e5979","model-score":0.28,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/services/member-access-report.service.ts b/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/services/member-access-report.service.ts\nindex 029dce8a40..0039788709 100644\n--- a/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/services/member-access-report.service.ts\n+++ b/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/services/member-access-report.service.ts\n@@ -7,3 +7,3 @@ import { I18nService } from \"@bitwarden/common/platform/abstractions/i18n.servic\n import { EncString } from \"@bitwarden/common/platform/models/domain/enc-string\";\n-import { OrganizationId } from \"@bitwarden/common/types/guid\";\n+import { Guid, OrganizationId } from \"@bitwarden/common/types/guid\";\n import {\n@@ -13,3 +13,3 @@ import {\n \n-import { MemberAccessDetails } from \"../response/member-access-report.response\";\n+import { MemberAccessResponse } from \"../response/member-access-report.response\";\n import { MemberAccessExportItem } from \"../view/member-access-export.view\";\n@@ -36,11 +36,40 @@ export class MemberAccessReportService {\n     const memberAccessData = await this.reportApiService.getMemberAccessData(organizationId);\n-    const memberAccessReportViewCollection = memberAccessData.map((userData) => ({\n-      name: userData.userName,\n-      email: userData.email,\n-      collectionsCount: userData.collectionsCount,\n-      groupsCount: userData.groupsCount,\n-      itemsCount: userData.totalItemCount,\n-      userGuid: userData.userGuid,\n-      usesKeyConnector: userData.usesKeyConnector,\n-    }));\n+\n+    // group member access data by userGuid\n+    const userMap = new Map<Guid, MemberAccessResponse[]>();\n+    memberAccessData.forEach((userData) => {\n+      const userGuid = userData.userGuid;\n+      if (!userMap.has(userGuid)) {\n+        userMap.set(userGuid, []);\n+      }\n+      userMap.get(userGuid)?.push(userData);\n+    });\n+\n+    // aggregate user data\n+    const memberAccessReportViewCollection: MemberAccessReportView[] = [];\n+    userMap.forEach((userDataArray, userGuid) => {\n+      const collectionCount = this.getDistinctCount<string>(\n+        userDataArray.map((data) => data.collectionId).filter((id) => !!id),\n+      );\n+      const groupCount = this.getDistinctCount<string>(\n+        userDataArray.map((data) => data.groupId).filter((id) => !!id),\n+      );\n+      const itemsCount = this.getDistinctCount<Guid>(\n+        userDataArray\n+          .flatMap((data) => data.cipherIds)\n+          .filter((id) => id !== \"00000000-0000-0000-0000-000000000000\"),\n+      );\n+      const aggregatedData = {\n+        userGuid: userGuid,\n+        name: userDataArray[0].userName,\n+        email: userDataArray[0].email,\n+        collectionsCount: collectionCount,\n+        groupsCount: groupCount,\n+        itemsCount: itemsCount,\n+        usesKeyConnector: userDataArray.some((data) => data.usesKeyConnector),\n+      };\n+\n+      memberAccessReportViewCollection.push(aggregatedData);\n+    });\n+\n     return memberAccessReportViewCollection;\n@@ -52,9 +81,4 @@ export class MemberAccessReportService {\n     const memberAccessReports = await this.reportApiService.getMemberAccessData(organizationId);\n-    const collectionNames = memberAccessReports.flatMap((item) =>\n-      item.accessDetails.map((dtl) => {\n-        if (dtl.collectionName) {\n-          return dtl.collectionName.encryptedString;\n-        }\n-      }),\n-    );\n+    const collectionNames = memberAccessReports.map((item) => item.collectionName.encryptedString);\n+\n     const collectionNameMap = new Map(collectionNames.map((col) => [col, \"\"]));\n@@ -66,47 +90,26 @@ export class MemberAccessReportService {\n \n-    const exportItems = memberAccessReports.flatMap((report) => {\n-      // to include users without access details\n-      // which means a user has no groups, collections or items\n-      if (report.accessDetails.length === 0) {\n-        return [\n-          {\n-            email: report.email,\n-            name: report.userName,\n-            twoStepLogin: report.twoFactorEnabled\n-              ? this.i18nService.t(\"memberAccessReportTwoFactorEnabledTrue\")\n-              : this.i18nService.t(\"memberAccessReportTwoFactorEnabledFalse\"),\n-            accountRecovery: report.accountRecoveryEnabled\n-              ? this.i18nService.t(\"memberAccessReportAuthenticationEnabledTrue\")\n-              : this.i18nService.t(\"memberAccessReportAuthenticationEnabledFalse\"),\n-            group: this.i18nService.t(\"memberAccessReportNoGroup\"),\n-            collection: this.i18nService.t(\"memberAccessReportNoCollection\"),\n-            collectionPermission: this.i18nService.t(\"memberAccessReportNoCollectionPermission\"),\n-            totalItems: \"0\",\n-          },\n-        ];\n-      }\n-      const userDetails = report.accessDetails.map((detail) => {\n-        const collectionName = collectionNameMap.get(detail.collectionName.encryptedString);\n-        return {\n-          email: report.email,\n-          name: report.userName,\n-          twoStepLogin: report.twoFactorEnabled\n-            ? this.i18nService.t(\"memberAccessReportTwoFactorEnabledTrue\")\n-            : this.i18nService.t(\"memberAccessReportTwoFactorEnabledFalse\"),\n-          accountRecovery: report.accountRecoveryEnabled\n-            ? this.i18nService.t(\"memberAccessReportAuthenticationEnabledTrue\")\n-            : this.i18nService.t(\"memberAccessReportAuthenticationEnabledFalse\"),\n-          group: detail.groupName\n-            ? detail.groupName\n-            : this.i18nService.t(\"memberAccessReportNoGroup\"),\n-          collection: collectionName\n-            ? collectionName\n-            : this.i18nService.t(\"memberAccessReportNoCollection\"),\n-          collectionPermission: detail.collectionId\n-            ? this.getPermissionText(detail)\n-            : this.i18nService.t(\"memberAccessReportNoCollectionPermission\"),\n-          totalItems: detail.itemCount.toString(),\n-        };\n-      });\n-      return userDetails;\n+    const exportItems = memberAccessReports.map((report) => {\n+      const collectionName = collectionNameMap.get(report.collectionName.encryptedString);\n+      return {\n+        email: report.email,\n+        name: report.userName,\n+        twoStepLogin: report.twoFactorEnabled\n+          ? this.i18nService.t(\"memberAccessReportTwoFactorEnabledTrue\")\n+          : this.i18nService.t(\"memberAccessReportTwoFactorEnabledFalse\"),\n+        accountRecovery: report.accountRecoveryEnabled\n+          ? this.i18nService.t(\"memberAccessReportAuthenticationEnabledTrue\")\n+          : this.i18nService.t(\"memberAccessReportAuthenticationEnabledFalse\"),\n+        group: report.groupName\n+          ? report.groupName\n+          : this.i18nService.t(\"memberAccessReportNoGroup\"),\n+        collection: collectionName\n+          ? collectionName\n+          : this.i18nService.t(\"memberAccessReportNoCollection\"),\n+        collectionPermission: report.collectionId\n+          ? this.getPermissionText(report)\n+          : this.i18nService.t(\"memberAccessReportNoCollectionPermission\"),\n+        totalItems: report.cipherIds\n+          .filter((_) => _ != \"00000000-0000-0000-0000-000000000000\")\n+          .length.toString(),\n+      };\n     });\n@@ -115,3 +118,3 @@ export class MemberAccessReportService {\n \n-  private getPermissionText(accessDetails: MemberAccessDetails): string {\n+  private getPermissionText(accessDetails: MemberAccessResponse): string {\n     const permissionList = getPermissionList();\n@@ -127,2 +130,7 @@ export class MemberAccessReportService {\n   }\n+\n+  private getDistinctCount<T>(items: T[]): number {\n+    const uniqueItems = new Set(items);\n+    return uniqueItems.size;\n+  }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Jimmy Vo","training-data":{"loc-added":"96","loc-deleted":"47","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.26","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"huynhmaivo82@gmail.com","commit-full-message":"It also includes a refactor to decouple the invite and edit user flows.","commit-date":"2025-01-23T19:24:51Z","current-rev":"620affd3d5","filename":"clients/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts","previous-rev":"aa1c0ca0ee","commit-title":"[PM-13755] Exclude revoked users from the occupied seats count (#12277)","language":"TypeScript","id":"9bc2793c21a8a04e666806c1a99624c875219202","model-score":0.27,"author-id":null,"project-id":26215,"delta-file-score":0.54150903,"diff":"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\nindex fbf29602e0..7a30eba9e1 100644\n--- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts\n+++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts\n@@ -57,2 +57,3 @@ import {\n import { commaSeparatedEmails } from \"./validators/comma-separated-emails.validator\";\n+import { inputEmailLimitValidator } from \"./validators/input-email-limit.validator\";\n import { orgSeatLimitReachedValidator } from \"./validators/org-seat-limit-reached.validator\";\n@@ -65,14 +66,24 @@ export enum MemberDialogTab {\n \n-export interface MemberDialogParams {\n-  name: string;\n+interface CommonMemberDialogParams {\n+  isOnSecretsManagerStandalone: boolean;\n   organizationId: string;\n-  organizationUserId: string;\n+}\n+\n+export interface AddMemberDialogParams extends CommonMemberDialogParams {\n+  kind: \"Add\";\n+  occupiedSeatCount: number;\n   allOrganizationUserEmails: string[];\n+}\n+\n+export interface EditMemberDialogParams extends CommonMemberDialogParams {\n+  kind: \"Edit\";\n+  name: string;\n+  organizationUserId: string;\n   usesKeyConnector: boolean;\n-  isOnSecretsManagerStandalone: boolean;\n-  initialTab?: MemberDialogTab;\n-  numSeatsUsed: number;\n   managedByOrganization?: boolean;\n+  initialTab: MemberDialogTab;\n }\n \n+export type MemberDialogParams = EditMemberDialogParams | AddMemberDialogParams;\n+\n export enum MemberDialogResult {\n@@ -100,2 +111,3 @@ export class MemberDialogComponent implements OnDestroy {\n   remainingSeats$: Observable<number>;\n+  editParams$: Observable<EditMemberDialogParams>;\n \n@@ -145,2 +157,8 @@ export class MemberDialogComponent implements OnDestroy {\n \n+  isEditDialogParams(\n+    params: EditMemberDialogParams | AddMemberDialogParams,\n+  ): params is EditMemberDialogParams {\n+    return params.kind === \"Edit\";\n+  }\n+\n   constructor(\n@@ -170,5 +188,20 @@ export class MemberDialogComponent implements OnDestroy {\n \n-    this.editMode = this.params.organizationUserId != null;\n-    this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;\n-    this.title = this.i18nService.t(this.editMode ? \"editMember\" : \"inviteMember\");\n+    let userDetails$;\n+    if (this.isEditDialogParams(this.params)) {\n+      this.editMode = true;\n+      this.title = this.i18nService.t(\"editMember\");\n+      userDetails$ = this.userService.get(\n+        this.params.organizationId,\n+        this.params.organizationUserId,\n+      );\n+      this.tabIndex = this.params.initialTab;\n+      this.editParams$ = of(this.params);\n+    } else {\n+      this.editMode = false;\n+      this.title = this.i18nService.t(\"inviteMember\");\n+      this.editParams$ = of(null);\n+      userDetails$ = of(null);\n+      this.tabIndex = MemberDialogTab.Role;\n+    }\n+\n     this.isOnSecretsManagerStandalone = this.params.isOnSecretsManagerStandalone;\n@@ -189,6 +222,2 @@ export class MemberDialogComponent implements OnDestroy {\n \n-    const userDetails$ = this.params.organizationUserId\n-      ? this.userService.get(this.params.organizationId, this.params.organizationUserId)\n-      : of(null);\n-\n     this.allowAdminAccessToAllCollectionItems$ = this.organization$.pipe(\n@@ -273,3 +302,9 @@ export class MemberDialogComponent implements OnDestroy {\n     this.remainingSeats$ = this.organization$.pipe(\n-      map((organization) => organization.seats - this.params.numSeatsUsed),\n+      map((organization) => {\n+        if (!this.isEditDialogParams(this.params)) {\n+          return organization.seats - this.params.occupiedSeatCount;\n+        }\n+\n+        return organization.seats;\n+      }),\n     );\n@@ -278,2 +313,6 @@ export class MemberDialogComponent implements OnDestroy {\n   private setFormValidators(organization: Organization) {\n+    if (this.isEditDialogParams(this.params)) {\n+      return;\n+    }\n+\n     const emailsControlValidators = [\n@@ -281,2 +320,5 @@ export class MemberDialogComponent implements OnDestroy {\n       commaSeparatedEmails,\n+      inputEmailLimitValidator(organization, (maxEmailsCount: number) =>\n+        this.i18nService.t(\"tooManyEmails\", maxEmailsCount),\n+      ),\n       orgSeatLimitReachedValidator(\n@@ -285,2 +327,3 @@ export class MemberDialogComponent implements OnDestroy {\n         this.i18nService.t(\"subscriptionUpgrade\", organization.seats),\n+        this.params.occupiedSeatCount,\n       ),\n@@ -435,6 +478,16 @@ export class MemberDialogComponent implements OnDestroy {\n \n+    const userView = await this.getUserView();\n+\n+    if (this.isEditDialogParams(this.params)) {\n+      await this.handleEditUser(userView, this.params);\n+    } else {\n+      await this.handleInviteUsers(userView, organization);\n+    }\n+  };\n+\n+  private async getUserView(): Promise<OrganizationUserAdminView> {\n     const userView = new OrganizationUserAdminView();\n-    userView.id = this.params.organizationUserId;\n     userView.organizationId = this.params.organizationId;\n     userView.type = this.formGroup.value.type;\n+\n     userView.permissions = this.setRequestPermissions(\n@@ -443,2 +496,3 @@ export class MemberDialogComponent implements OnDestroy {\n     );\n+\n     userView.collections = this.formGroup.value.access\n@@ -453,26 +507,11 @@ export class MemberDialogComponent implements OnDestroy {\n \n-    if (this.editMode) {\n-      await this.userService.save(userView);\n-    } else {\n-      userView.id = this.params.organizationUserId;\n-      const maxEmailsCount =\n-        organization.productTierType === ProductTierType.TeamsStarter ? 10 : 20;\n-      const emails = [...new Set(this.formGroup.value.emails.trim().split(/\\s*,\\s*/))];\n-      if (emails.length > maxEmailsCount) {\n-        this.formGroup.controls.emails.setErrors({\n-          tooManyEmails: { message: this.i18nService.t(\"tooManyEmails\", maxEmailsCount) },\n-        });\n-        return;\n-      }\n-      if (\n-        organization.hasReseller &&\n-        this.params.numSeatsUsed + emails.length > organization.seats\n-      ) {\n-        this.formGroup.controls.emails.setErrors({\n-          tooManyEmails: { message: this.i18nService.t(\"seatLimitReachedContactYourProvider\") },\n-        });\n-        return;\n-      }\n-      await this.userService.invite(emails, userView);\n-    }\n+    return userView;\n+  }\n+\n+  private async handleEditUser(\n+    userView: OrganizationUserAdminView,\n+    params: EditMemberDialogParams,\n+  ) {\n+    userView.id = params.organizationUserId;\n+    await this.userService.save(userView);\n \n@@ -481,12 +520,23 @@ export class MemberDialogComponent implements OnDestroy {\n       title: null,\n-      message: this.i18nService.t(\n-        this.editMode ? \"editedUserId\" : \"invitedUsers\",\n-        this.params.name,\n-      ),\n+      message: this.i18nService.t(\"editedUserId\", params.name),\n     });\n+\n     this.close(MemberDialogResult.Saved);\n-  };\n+  }\n+\n+  private async handleInviteUsers(userView: OrganizationUserAdminView, organization: Organization) {\n+    const emails = [...new Set(this.formGroup.value.emails.trim().split(/\\s*,\\s*/))];\n+\n+    await this.userService.invite(emails, userView);\n+\n+    this.toastService.showToast({\n+      variant: \"success\",\n+      title: null,\n+      message: this.i18nService.t(\"invitedUsers\"),\n+    });\n+    this.close(MemberDialogResult.Saved);\n+  }\n \n   remove = async () => {\n-    if (!this.editMode) {\n+    if (!this.isEditDialogParams(this.params)) {\n       return;\n@@ -509,3 +559,3 @@ export class MemberDialogComponent implements OnDestroy {\n     if (this.showNoMasterPasswordWarning) {\n-      confirmed = await this.noMasterPasswordConfirmationDialog();\n+      confirmed = await this.noMasterPasswordConfirmationDialog(this.params.name);\n \n@@ -530,3 +580,3 @@ export class MemberDialogComponent implements OnDestroy {\n   revoke = async () => {\n-    if (!this.editMode) {\n+    if (!this.isEditDialogParams(this.params)) {\n       return;\n@@ -546,3 +596,3 @@ export class MemberDialogComponent implements OnDestroy {\n     if (this.showNoMasterPasswordWarning) {\n-      confirmed = await this.noMasterPasswordConfirmationDialog();\n+      confirmed = await this.noMasterPasswordConfirmationDialog(this.params.name);\n \n@@ -568,3 +618,3 @@ export class MemberDialogComponent implements OnDestroy {\n   restore = async () => {\n-    if (!this.editMode) {\n+    if (!this.isEditDialogParams(this.params)) {\n       return;\n@@ -587,3 +637,3 @@ export class MemberDialogComponent implements OnDestroy {\n   delete = async () => {\n-    if (!this.editMode) {\n+    if (!this.isEditDialogParams(this.params)) {\n       return;\n@@ -635,3 +685,3 @@ export class MemberDialogComponent implements OnDestroy {\n \n-  private noMasterPasswordConfirmationDialog() {\n+  private noMasterPasswordConfirmationDialog(username: string) {\n     return this.dialogService.openSimpleDialog({\n@@ -642,3 +692,3 @@ export class MemberDialogComponent implements OnDestroy {\n         key: \"removeOrgUserNoMasterPasswordDesc\",\n-        placeholders: [this.params.name],\n+        placeholders: [username],\n       },\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Oscar Hinton","training-data":{"loc-added":"57","loc-deleted":"84","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"Hinton@users.noreply.github.com","commit-full-message":"We should be able to simplify the angular component by using svg element directly.","commit-date":"2025-10-03T09:51:25Z","current-rev":"a1e226b598","filename":"clients/libs/components/src/avatar/avatar.component.ts","previous-rev":"cb20889a94","commit-title":"[PM-25603] Use angular template for avatar (#15978)","language":"TypeScript","id":"89bc3476b12fde8b67cf202f6f5bbba0a4a5e98a","model-score":0.26,"author-id":null,"project-id":26215,"delta-file-score":0.61278176,"diff":"diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts\nindex 6f83c9ca10..8ece033c73 100644\n--- a/libs/components/src/avatar/avatar.component.ts\n+++ b/libs/components/src/avatar/avatar.component.ts\n@@ -1,4 +1,3 @@\n import { NgClass } from \"@angular/common\";\n-import { Component, OnChanges, input } from \"@angular/core\";\n-import { DomSanitizer, SafeResourceUrl } from \"@angular/platform-browser\";\n+import { Component, computed, input } from \"@angular/core\";\n \n@@ -24,8 +23,30 @@ const SizeClasses: Record<SizeTypes, string[]> = {\n   selector: \"bit-avatar\",\n-  template: `@if (src) {\n-    <img [src]=\"src\" title=\"{{ title() || text() }}\" [ngClass]=\"classList\" />\n-  }`,\n+  template: `\n+    <span [title]=\"title() || text()\">\n+      <svg\n+        xmlns=\"http://www.w3.org/2000/svg\"\n+        pointer-events=\"none\"\n+        [style.backgroundColor]=\"backgroundColor()\"\n+        [ngClass]=\"classList()\"\n+        attr.viewBox=\"0 0 {{ svgSize }} {{ svgSize }}\"\n+      >\n+        <text\n+          text-anchor=\"middle\"\n+          y=\"50%\"\n+          x=\"50%\"\n+          dy=\"0.35em\"\n+          pointer-events=\"auto\"\n+          [attr.fill]=\"textColor()\"\n+          [style.fontWeight]=\"svgFontWeight\"\n+          [style.fontSize.px]=\"svgFontSize\"\n+          font-family='Roboto,\"Helvetica Neue\",Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\"'\n+        >\n+          {{ displayChars() }}\n+        </text>\n+      </svg>\n+    </span>\n+  `,\n   imports: [NgClass],\n })\n-export class AvatarComponent implements OnChanges {\n+export class AvatarComponent {\n   readonly border = input(false);\n@@ -37,32 +58,36 @@ export class AvatarComponent implements OnChanges {\n \n-  private svgCharCount = 2;\n-  private svgFontSize = 20;\n-  private svgFontWeight = 300;\n-  private svgSize = 48;\n-  src?: SafeResourceUrl;\n+  protected readonly svgCharCount = 2;\n+  protected readonly svgFontSize = 20;\n+  protected readonly svgFontWeight = 300;\n+  protected readonly svgSize = 48;\n \n-  constructor(public sanitizer: DomSanitizer) {}\n-\n-  ngOnChanges() {\n-    this.generate();\n-  }\n-\n-  get classList() {\n-    return [\"tw-rounded-full\", \"tw-inline\"]\n+  protected readonly classList = computed(() => {\n+    return [\"tw-rounded-full\"]\n       .concat(SizeClasses[this.size()] ?? [])\n       .concat(this.border() ? [\"tw-border\", \"tw-border-solid\", \"tw-border-secondary-600\"] : []);\n-  }\n+  });\n \n-  private generate() {\n-    const color = this.color();\n-    const text = this.text();\n+  protected readonly backgroundColor = computed(() => {\n     const id = this.id();\n-    if (!text && !color && !id) {\n-      throw new Error(\"Must supply `text`, `color`, or `id` input.\");\n+    const upperCaseText = this.text()?.toUpperCase() ?? \"\";\n+\n+    if (!Utils.isNullOrWhitespace(this.color())) {\n+      return this.color()!;\n     }\n-    let chars: string | null = null;\n-    const upperCaseText = text?.toUpperCase() ?? \"\";\n \n-    chars = this.getFirstLetters(upperCaseText, this.svgCharCount);\n+    if (!Utils.isNullOrWhitespace(id)) {\n+      return Utils.stringToColor(id!.toString());\n+    }\n+\n+    return Utils.stringToColor(upperCaseText);\n+  });\n+\n+  protected readonly textColor = computed(() => {\n+    return Utils.pickTextColorBasedOnBgColor(this.backgroundColor(), 135, true);\n+  });\n+\n+  protected readonly displayChars = computed(() => {\n+    const upperCaseText = this.text()?.toUpperCase() ?? \"\";\n \n+    let chars = this.getFirstLetters(upperCaseText, this.svgCharCount);\n     if (chars == null) {\n@@ -77,26 +102,6 @@ export class AvatarComponent implements OnChanges {\n \n-    let svg: HTMLElement;\n-    let hexColor = color ?? \"\";\n-    if (!Utils.isNullOrWhitespace(hexColor)) {\n-      svg = this.createSvgElement(this.svgSize, hexColor);\n-    } else if (!Utils.isNullOrWhitespace(id ?? \"\")) {\n-      hexColor = Utils.stringToColor(id!.toString());\n-      svg = this.createSvgElement(this.svgSize, hexColor);\n-    } else {\n-      hexColor = Utils.stringToColor(upperCaseText);\n-      svg = this.createSvgElement(this.svgSize, hexColor);\n-    }\n-\n-    const charObj = this.createTextElement(chars, hexColor);\n-    svg.appendChild(charObj);\n-    const html = window.document.createElement(\"div\").appendChild(svg).outerHTML;\n-    const svgHtml = window.btoa(unescape(encodeURIComponent(html)));\n-\n-    // This is safe because the only user provided value, chars is set using `textContent`\n-    this.src = this.sanitizer.bypassSecurityTrustResourceUrl(\n-      \"data:image/svg+xml;base64,\" + svgHtml,\n-    );\n-  }\n+    return chars;\n+  });\n \n-  private getFirstLetters(data: string, count: number): string | null {\n+  private getFirstLetters(data: string, count: number): string | undefined {\n     const parts = data.split(\" \");\n@@ -109,35 +114,3 @@ export class AvatarComponent implements OnChanges {\n     }\n-    return null;\n-  }\n-\n-  private createSvgElement(size: number, color: string): HTMLElement {\n-    const svgTag = window.document.createElement(\"svg\");\n-    svgTag.setAttribute(\"xmlns\", \"http://www.w3.org/2000/svg\");\n-    svgTag.setAttribute(\"pointer-events\", \"none\");\n-    svgTag.setAttribute(\"width\", size.toString());\n-    svgTag.setAttribute(\"height\", size.toString());\n-    svgTag.style.backgroundColor = color;\n-    svgTag.style.width = size + \"px\";\n-    svgTag.style.height = size + \"px\";\n-    return svgTag;\n-  }\n-\n-  private createTextElement(character: string, color: string): HTMLElement {\n-    const textTag = window.document.createElement(\"text\");\n-    textTag.setAttribute(\"text-anchor\", \"middle\");\n-    textTag.setAttribute(\"y\", \"50%\");\n-    textTag.setAttribute(\"x\", \"50%\");\n-    textTag.setAttribute(\"dy\", \"0.35em\");\n-    textTag.setAttribute(\"pointer-events\", \"auto\");\n-    textTag.setAttribute(\"fill\", Utils.pickTextColorBasedOnBgColor(color, 135, true));\n-    textTag.setAttribute(\n-      \"font-family\",\n-      'Roboto,\"Helvetica Neue\",Helvetica,Arial,' +\n-        'sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\"',\n-    );\n-    // Warning do not use innerHTML here, characters are user provided\n-    textTag.textContent = character;\n-    textTag.style.fontWeight = this.svgFontWeight.toString();\n-    textTag.style.fontSize = this.svgFontSize + \"px\";\n-    return textTag;\n+    return undefined;\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Bernd Schoolmann","training-data":{"loc-added":"20","loc-deleted":"30","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"mail@quexten.com","commit-full-message":"* Remove AES128CBC-HMAC encryption\n\n* Increase test coverage\n\n* Refactor symmetric keys and increase test coverage\n\n* Re-add type 0 encryption\n\n* Fix ts strict warning\n\n* Remove old symmetric key representations in symmetriccryptokey\n\n* Fix desktop build\n\n* Fix test\n\n* Fix build\n\n* Update libs/common/src/key-management/crypto/services/web-crypto-function.service.ts\n\nCo-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>\n\n* Update libs/node/src/services/node-crypto-function.service.ts\n\nCo-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>\n\n* Undo changes\n\n* Remove cast\n\n* Undo changes to tests\n\n* Fix linting\n\n* Undo removing new Uint8Array in aesDecryptFastParameters\n\n* Fix merge conflicts\n\n* Fix test\n\n* Fix another test\n\n* Fix test\n\n---------\n\nCo-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>","commit-date":"2025-04-21T14:57:26Z","current-rev":"43b1f55360","filename":"clients/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts","previous-rev":"a250395e6d","commit-title":"[PM-18697] Remove old symmetric key representations in symmetriccryptokey (#13598)","language":"TypeScript","id":"95ec8c7e09f0d752e86808fcf9854247a648fb6b","model-score":0.26,"author-id":null,"project-id":26215,"delta-file-score":0.3009901,"diff":"diff --git a/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts b/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts\nindex 0c80d508b2..430774ca2e 100644\n--- a/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts\n+++ b/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts\n@@ -3,2 +3,3 @@ import * as forge from \"node-forge\";\n \n+import { EncryptionType } from \"../../../platform/enums\";\n import { Utils } from \"../../../platform/misc/utils\";\n@@ -249,33 +250,22 @@ export class WebCryptoFunctionService implements CryptoFunctionService {\n   ): CbcDecryptParameters<string> {\n-    const p = {} as CbcDecryptParameters<string>;\n-    if (key.meta != null) {\n-      p.encKey = key.meta.encKeyByteString;\n-      p.macKey = key.meta.macKeyByteString;\n-    }\n-\n-    if (p.encKey == null) {\n-      p.encKey = forge.util.decode64(key.encKeyB64);\n-    }\n-    p.data = forge.util.decode64(data);\n-    p.iv = forge.util.decode64(iv);\n-    p.macData = p.iv + p.data;\n-    if (p.macKey == null && key.macKeyB64 != null) {\n-      p.macKey = forge.util.decode64(key.macKeyB64);\n-    }\n-    if (mac != null) {\n-      p.mac = forge.util.decode64(mac);\n-    }\n-\n-    // cache byte string keys for later\n-    if (key.meta == null) {\n-      key.meta = {};\n-    }\n-    if (key.meta.encKeyByteString == null) {\n-      key.meta.encKeyByteString = p.encKey;\n-    }\n-    if (p.macKey != null && key.meta.macKeyByteString == null) {\n-      key.meta.macKeyByteString = p.macKey;\n+    const innerKey = key.inner();\n+    if (innerKey.type === EncryptionType.AesCbc256_B64) {\n+      return {\n+        iv: forge.util.decode64(iv),\n+        data: forge.util.decode64(data),\n+        encKey: forge.util.createBuffer(innerKey.encryptionKey).getBytes(),\n+      } as CbcDecryptParameters<string>;\n+    } else if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {\n+      const macData = forge.util.decode64(iv) + forge.util.decode64(data);\n+      return {\n+        iv: forge.util.decode64(iv),\n+        data: forge.util.decode64(data),\n+        encKey: forge.util.createBuffer(innerKey.encryptionKey).getBytes(),\n+        macKey: forge.util.createBuffer(innerKey.authenticationKey).getBytes(),\n+        mac: forge.util.decode64(mac!),\n+        macData,\n+      } as CbcDecryptParameters<string>;\n+    } else {\n+      throw new Error(\"Unsupported encryption type.\");\n     }\n-\n-    return p;\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Jordan Aasen","training-data":{"loc-added":"1","loc-deleted":"59","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"166539328+jaasen-livefront@users.noreply.github.com","commit-full-message":"","commit-date":"2025-06-18T22:27:34Z","current-rev":"f9b31d2906","filename":"clients/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts","previous-rev":"b35583a5ac","commit-title":"remove legacy attachment upload (#15237)","language":"TypeScript","id":"663ea4eacb6195e4d6cf8d5d5ece4e6e22fa48d3","model-score":0.24,"author-id":null,"project-id":26215,"delta-file-score":0.59155285,"diff":"diff --git a/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts b/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts\nindex 4dd2f7f733..10fa1d9580 100644\n--- a/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts\n+++ b/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts\n@@ -8,3 +8,2 @@ import {\n } from \"../../../platform/abstractions/file-upload/file-upload.service\";\n-import { Utils } from \"../../../platform/misc/utils\";\n import { EncArrayBuffer } from \"../../../platform/models/domain/enc-array-buffer\";\n@@ -49,14 +48,3 @@ export class CipherFileUploadService implements CipherFileUploadServiceAbstracti\n     } catch (e) {\n-      if (\n-        (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) ||\n-        (e as ErrorResponse).statusCode === 405\n-      ) {\n-        response = await this.legacyServerAttachmentFileUpload(\n-          request.adminRequest,\n-          cipher.id,\n-          encFileName,\n-          encData,\n-          dataEncKey[1],\n-        );\n-      } else if (e instanceof ErrorResponse) {\n+      if (e instanceof ErrorResponse) {\n         throw new Error((e as ErrorResponse).getSingleMessage());\n@@ -115,48 +103,2 @@ export class CipherFileUploadService implements CipherFileUploadServiceAbstracti\n   }\n-\n-  /**\n-   * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.\n-   * This method still exists for backward compatibility with old server versions.\n-   */\n-  async legacyServerAttachmentFileUpload(\n-    admin: boolean,\n-    cipherId: string,\n-    encFileName: EncString,\n-    encData: EncArrayBuffer,\n-    key: EncString,\n-  ) {\n-    const fd = new FormData();\n-    try {\n-      const blob = new Blob([encData.buffer], { type: \"application/octet-stream\" });\n-      fd.append(\"key\", key.encryptedString);\n-      fd.append(\"data\", blob, encFileName.encryptedString);\n-    } catch (e) {\n-      if (Utils.isNode && !Utils.isBrowser) {\n-        fd.append(\"key\", key.encryptedString);\n-        fd.append(\n-          \"data\",\n-          Buffer.from(encData.buffer) as any,\n-          {\n-            filepath: encFileName.encryptedString,\n-            contentType: \"application/octet-stream\",\n-          } as any,\n-        );\n-      } else {\n-        throw e;\n-      }\n-    }\n-\n-    let response: CipherResponse;\n-    try {\n-      if (admin) {\n-        response = await this.apiService.postCipherAttachmentAdminLegacy(cipherId, fd);\n-      } else {\n-        response = await this.apiService.postCipherAttachmentLegacy(cipherId, fd);\n-      }\n-    } catch (e) {\n-      throw new Error((e as ErrorResponse).getSingleMessage());\n-    }\n-\n-    return response;\n-  }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Jared Snider","training-data":{"loc-added":"65","loc-deleted":"30","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.536386775820924"},"author-email":"116684653+JaredSnider-Bitwarden@users.noreply.github.com","commit-full-message":"* PM-8112 - Update classes of existing registration icons\r\n\r\n* PM-8112 - Add new icons\r\n\r\n* PM-8112 - Export icons from libs/auth\r\n\r\n* PM-8112 - RegistrationStart - Add new user icon as page icon\r\n\r\n* PM-8112 - Replace RegistrationCheckEmailIcon with new icon so it displays properly\r\n\r\n* PM-8112 - RegistrationFinish - Add new icon across clients\r\n\r\n* PM-8112 - Registration start comp - update page icon and page title on state change to match figma\r\n\r\n* PM-8112 - RegistrationFinish - adding most of framework for changing page title & subtitle when an org invite is in state.\r\n\r\n* PM-8112 - Add joinOrganizationName to all clients translations\r\n\r\n* PM-8112 - RegistrationFinish - Remove default page title & subtitle and let onInit logic figure out what to set based on flows.\r\n\r\n* PM-8112 - RegistrationStart - Fix setAnonLayoutWrapperData calls\r\n\r\n* PM-8112 - RegistrationFinish - simplify qParams init logic to make handling loading and page title and subtitle setting easier.\r\n\r\n* PM-8112 - Registration Link expired - move icon to page icon out of main content\r\n\r\n* PM-8112 - RegistrationFinish - Refactor init logic further into distinct flows.\r\n\r\n* PM-8112 - Fix icons\r\n\r\n* PM-8112 - Extension AppRoutingModule - move sign up start & finish routes under extension anon layout\r\n\r\n* PM-8112 - Fix storybook\r\n\r\n* PM-8112 - Clean up unused prop\r\n\r\n* PM-8112 - RegistrationLockAltIcon tweaks\r\n\r\n* PM-8112 - Update icons to have proper styling\r\n\r\n* PM-8112 - RegistrationUserAddIcon - remove unnecessary svg class\r\n\r\n* PM-8112 - Fix icons","commit-date":"2024-10-16T22:28:27Z","current-rev":"4b67cd24b4","filename":"clients/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts","previous-rev":"e256bde1de","commit-title":"Auth/PM-8112 - UI refresh - Registration Components (#11353)","language":"TypeScript","id":"61412453a0eb48371e48935d1a4f4c745cc04914","model-score":0.16,"author-id":null,"project-id":26215,"delta-file-score":0.29573047,"diff":"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\nindex ef40d95dce..be9d8abe5b 100644\n--- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts\n+++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts\n@@ -3,3 +3,3 @@ import { Component, OnDestroy, OnInit } from \"@angular/core\";\n import { ActivatedRoute, Params, Router, RouterModule } from \"@angular/router\";\n-import { EMPTY, Subject, from, switchMap, takeUntil, tap } from \"rxjs\";\n+import { Subject, firstValueFrom } from \"rxjs\";\n \n@@ -17,2 +17,3 @@ import { ToastService } from \"@bitwarden/components\";\n import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from \"../../../common\";\n+import { AnonLayoutWrapperDataService } from \"../../anon-layout/anon-layout-wrapper-data.service\";\n import { InputPasswordComponent } from \"../../input-password/input-password.component\";\n@@ -62,2 +63,3 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n     private logService: LogService,\n+    private anonLayoutWrapperDataService: AnonLayoutWrapperDataService,\n   ) {}\n@@ -65,48 +67,64 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n   async ngOnInit() {\n-    this.listenForQueryParamChanges();\n-    this.masterPasswordPolicyOptions =\n-      await this.registrationFinishService.getMasterPasswordPolicyOptsFromOrgInvite();\n+    const qParams = await firstValueFrom(this.activatedRoute.queryParams);\n+    this.handleQueryParams(qParams);\n+\n+    if (\n+      qParams.fromEmail &&\n+      qParams.fromEmail === \"true\" &&\n+      this.email &&\n+      this.emailVerificationToken\n+    ) {\n+      await this.initEmailVerificationFlow();\n+    } else {\n+      // Org Invite flow OR registration with email verification disabled Flow\n+      const orgInviteFlow = await this.initOrgInviteFlowIfPresent();\n+\n+      if (!orgInviteFlow) {\n+        this.initRegistrationWithEmailVerificationDisabledFlow();\n+      }\n+    }\n+\n+    this.loading = false;\n   }\n \n-  private listenForQueryParamChanges() {\n-    this.activatedRoute.queryParams\n-      .pipe(\n-        tap((qParams: Params) => {\n-          if (qParams.email != null && qParams.email.indexOf(\"@\") > -1) {\n-            this.email = qParams.email;\n-          }\n+  private handleQueryParams(qParams: Params) {\n+    if (qParams.email != null && qParams.email.indexOf(\"@\") > -1) {\n+      this.email = qParams.email;\n+    }\n \n-          if (qParams.token != null) {\n-            this.emailVerificationToken = qParams.token;\n-          }\n+    if (qParams.token != null) {\n+      this.emailVerificationToken = qParams.token;\n+    }\n \n-          if (qParams.orgSponsoredFreeFamilyPlanToken != null) {\n-            this.orgSponsoredFreeFamilyPlanToken = qParams.orgSponsoredFreeFamilyPlanToken;\n-          }\n+    if (qParams.orgSponsoredFreeFamilyPlanToken != null) {\n+      this.orgSponsoredFreeFamilyPlanToken = qParams.orgSponsoredFreeFamilyPlanToken;\n+    }\n \n-          if (qParams.acceptEmergencyAccessInviteToken != null && qParams.emergencyAccessId) {\n-            this.acceptEmergencyAccessInviteToken = qParams.acceptEmergencyAccessInviteToken;\n-            this.emergencyAccessId = qParams.emergencyAccessId;\n-          }\n-        }),\n-        switchMap((qParams: Params) => {\n-          if (\n-            qParams.fromEmail &&\n-            qParams.fromEmail === \"true\" &&\n-            this.email &&\n-            this.emailVerificationToken\n-          ) {\n-            return from(\n-              this.registerVerificationEmailClicked(this.email, this.emailVerificationToken),\n-            );\n-          } else {\n-            // org invite flow\n-            this.loading = false;\n-            return EMPTY;\n-          }\n-        }),\n+    if (qParams.acceptEmergencyAccessInviteToken != null && qParams.emergencyAccessId) {\n+      this.acceptEmergencyAccessInviteToken = qParams.acceptEmergencyAccessInviteToken;\n+      this.emergencyAccessId = qParams.emergencyAccessId;\n+    }\n+  }\n+\n+  private async initOrgInviteFlowIfPresent(): Promise<boolean> {\n+    this.masterPasswordPolicyOptions =\n+      await this.registrationFinishService.getMasterPasswordPolicyOptsFromOrgInvite();\n+\n+    const orgName = await this.registrationFinishService.getOrgNameFromOrgInvite();\n+    if (orgName) {\n+      // Org invite exists\n+      // Set the page title and subtitle appropriately\n+      this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({\n+        pageTitle: {\n+          key: \"joinOrganizationName\",\n+          placeholders: [orgName],\n+        },\n+        pageSubtitle: {\n+          key: \"finishJoiningThisOrganizationBySettingAMasterPassword\",\n+        },\n+      });\n+      return true;\n+    }\n \n-        takeUntil(this.destroy$),\n-      )\n-      .subscribe();\n+    return false;\n   }\n@@ -164,5 +182,20 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n \n+  private setDefaultPageTitleAndSubtitle() {\n+    this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({\n+      pageTitle: {\n+        key: \"setAStrongPassword\",\n+      },\n+      pageSubtitle: {\n+        key: \"finishCreatingYourAccountBySettingAPassword\",\n+      },\n+    });\n+  }\n+\n+  private async initEmailVerificationFlow() {\n+    this.setDefaultPageTitleAndSubtitle();\n+    await this.registerVerificationEmailClicked(this.email, this.emailVerificationToken);\n+  }\n+\n   private async registerVerificationEmailClicked(email: string, emailVerificationToken: string) {\n     const request = new RegisterVerificationEmailClickedRequest(email, emailVerificationToken);\n-\n     try {\n@@ -176,3 +209,2 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n         });\n-        this.loading = false;\n       }\n@@ -180,3 +212,2 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n       await this.handleRegisterVerificationEmailClickedError(e);\n-      this.loading = false;\n     }\n@@ -206,2 +237,6 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n \n+  private initRegistrationWithEmailVerificationDisabledFlow() {\n+    this.setDefaultPageTitleAndSubtitle();\n+  }\n+\n   ngOnDestroy(): void {\n","improvement-type":"Complex Method"}],"change-level":"warning","is-hotspot?":false,"line":48,"what-changed":"CollectionView.canDelete increases in cyclomatic complexity from 12 to 13, threshold = 9","how-to-fix":"There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html). Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring.","change-type":"degraded"},{"method":"CollectionView.canEdit","why-it-occurs":"A Complex Method has a high cyclomatic complexity. The recommended threshold for the TypeScript language is a cyclomatic complexity lower than 9.","name":"Complex Method","file":"libs/common/src/vault/models/view/collection.view.ts","refactoring-examples":[{"architectural-component-id":null,"author-name":"Jason Ng","training-data":{"loc-added":"2","loc-deleted":"5","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"jng@bitwarden.com","commit-full-message":"","commit-date":"2026-02-11T14:53:46Z","current-rev":"d18ddd3480","filename":"clients/apps/desktop/src/vault/app/vault/item-footer.component.ts","previous-rev":"4852f8fc4f","commit-title":"[PM-31680] remove archive buttons from footer for edit view desktop (#18858)","language":"TypeScript","id":"2bb04750258319dacf9c39935ef94d8ee4aea73e","model-score":0.96,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.ts b/apps/desktop/src/vault/app/vault/item-footer.component.ts\nindex 02c9873c29..133a9777fa 100644\n--- a/apps/desktop/src/vault/app/vault/item-footer.component.ts\n+++ b/apps/desktop/src/vault/app/vault/item-footer.component.ts\n@@ -265,6 +265,3 @@ export class ItemFooterComponent implements OnInit, OnChanges {\n     this.showArchiveButton =\n-      cipherCanBeArchived &&\n-      userCanArchive &&\n-      (this.action === \"view\" || this.action === \"edit\") &&\n-      !this.cipher.isArchived;\n+      cipherCanBeArchived && userCanArchive && this.action === \"view\" && !this.cipher.isArchived;\n \n@@ -273,3 +270,3 @@ export class ItemFooterComponent implements OnInit, OnChanges {\n       hasArchiveFlagEnabled &&\n-      (this.action === \"view\" || this.action === \"edit\") &&\n+      this.action === \"view\" &&\n       this.cipher.isArchived &&\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nick Krantz","training-data":{"loc-added":"4","loc-deleted":"2","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"125900171+nick-livefront@users.noreply.github.com","commit-full-message":"* remove todo\n\n* Retrieve cache cipher for add-edit form\n\n* user prefilled cipher for add-edit form\n\n* add listener for clearing view cache\n\n* clear local cache when clearing global state\n\n* track initial value of cache for down stream logic that should only occur on non-cached values\n\n* add feature flag for edit form persistence\n\n* add tests for cipher form cache service\n\n* fix optional initialValues\n\n* add services to cipher form storybook\n\n* fix strict types\n\n* rename variables to be platform agnostic\n\n* use deconstructed collectionIds variable to avoid them be overwritten\n\n* use the originalCipherView for initial values\n\n* add comment about signal equality\n\n* prevent events from being emitted when adding uris to the existing form\n\n- This stops other values from being overwrote in the initialization process\n\n* add check for cached cipher when adding initial uris","commit-date":"2025-01-22T16:49:07Z","current-rev":"5c32e5020d","filename":"clients/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts","previous-rev":"1dfae06856","commit-title":"[PM-9111] Extension: persist add/edit form (#12236)","language":"TypeScript","id":"137e2efd304af2a9a4d5c9d5cfddae574adf1def","model-score":0.95,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts\nindex cd75fe8fba..f17432a993 100644\n--- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts\n+++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts\n@@ -150,4 +150,6 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {\n \n-    // Populate the form with the existing fields\n-    this.cipherFormContainer.originalCipherView?.fields?.forEach((field) => {\n+    const prefillCipher = this.cipherFormContainer.getInitialCipherView();\n+\n+    // When available, populate the form with the existing fields\n+    prefillCipher.fields?.forEach((field) => {\n       let value: string | boolean = field.value;\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Jared","training-data":{"loc-added":"2","loc-deleted":"8","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"TheWolfBadger@gmail.com","commit-full-message":"* Refactor: Remove ConfigService dependency from auto-confirm related components\n\n- Eliminated ConfigService from MainBackground, AutoConfirmPolicy, UserLayoutComponent, WebVaultPromptService, and organizationPolicyGuard.\n- Updated logic to directly use organization properties instead of feature flags for auto-confirm functionality.\n- Adjusted tests in DefaultAutomaticUserConfirmationService to reflect the removal of feature flag checks.\n- Cleaned up unused imports related to ConfigService across various files.\n\n* Refactor: Update date handling in tests and remove unused feature flag checks\n\n- Changed date calculation in WebVaultExtensionPromptService tests to use milliseconds for accuracy.\n- Removed unused feature flag checks from WebVaultPromptService tests, simplifying the logic and improving clarity.\n\n* Refactor: Update organizationPolicyGuard to include ConfigService in feature callback\n\n- Modified the organizationPolicyGuard to accept ConfigService as an additional parameter in the feature callback.\n- Adjusted the SendComponent route to align with the updated guard implementation.\n\n* Fix: Adjust date calculation in WebVaultExtensionPromptService tests for accuracy\n\n- Updated the test to set the exact date to 30 days prior using setDate method for clarity and to avoid potential issues with DST boundaries.","commit-date":"2026-04-09T19:36:38Z","current-rev":"79c6b51599","filename":"clients/apps/web/src/app/vault/services/web-vault-prompt.service.ts","previous-rev":"14cd2ad341","commit-title":"[PM-26383] Remove feature flag to enable autoconfirm (#20015)","language":"TypeScript","id":"c2a6f627470bdf03b24f828d7fc922c6b996e272","model-score":0.78,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/web/src/app/vault/services/web-vault-prompt.service.ts b/apps/web/src/app/vault/services/web-vault-prompt.service.ts\nindex fd42d116d1..84dd4d281b 100644\n--- a/apps/web/src/app/vault/services/web-vault-prompt.service.ts\n+++ b/apps/web/src/app/vault/services/web-vault-prompt.service.ts\n@@ -10,4 +10,2 @@ import { AccountService } from \"@bitwarden/common/auth/abstractions/account.serv\n import { getUserId } from \"@bitwarden/common/auth/services/account.service\";\n-import { FeatureFlag } from \"@bitwarden/common/enums/feature-flag.enum\";\n-import { ConfigService } from \"@bitwarden/common/platform/abstractions/config/config.service\";\n import { DialogService } from \"@bitwarden/components\";\n@@ -33,3 +31,2 @@ export class WebVaultPromptService {\n   private organizationService = inject(OrganizationService);\n-  private configService = inject(ConfigService);\n   private dialogService = inject(DialogService);\n@@ -79,4 +76,2 @@ export class WebVaultPromptService {\n \n-    const featureFlag$ = this.configService.getFeatureFlag$(FeatureFlag.AutoConfirm);\n-\n     const autoConfirmState$ = this.userId$.pipe(\n@@ -97,8 +92,7 @@ export class WebVaultPromptService {\n \n-    zip([organization$, featureFlag$, autoConfirmState$, policyEnabled$, this.userId$])\n+    zip([organization$, autoConfirmState$, policyEnabled$, this.userId$])\n       .pipe(\n         first(),\n-        switchMap(async ([organization, flagEnabled, autoConfirmState, policyEnabled, userId]) => {\n+        switchMap(async ([organization, autoConfirmState, policyEnabled, userId]) => {\n           const showDialog =\n-            flagEnabled &&\n             !policyEnabled &&\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vicki League","training-data":{"loc-added":"5","loc-deleted":"5","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"vleague@bitwarden.com","commit-full-message":"","commit-date":"2026-01-16T14:36:00Z","current-rev":"12516ceeea","filename":"clients/libs/components/src/menu/menu-trigger-for.directive.ts","previous-rev":"5dee97158a","commit-title":"[PM-24178] Handle dialog focus when launched from a menu item (#18208)","language":"TypeScript","id":"c30f02246a0aaf050b3444858baeb1d3447a8b8c","model-score":0.77,"author-id":null,"project-id":26215,"delta-file-score":0.59155285,"diff":"diff --git a/libs/components/src/menu/menu-trigger-for.directive.ts b/libs/components/src/menu/menu-trigger-for.directive.ts\nindex 1d79fbc976..6306f3326d 100644\n--- a/libs/components/src/menu/menu-trigger-for.directive.ts\n+++ b/libs/components/src/menu/menu-trigger-for.directive.ts\n@@ -194,3 +194,3 @@ export class MenuTriggerForDirective implements OnDestroy {\n \n-    const escKey = this.overlayRef.keydownEvents().pipe(\n+    const keyEvents = this.overlayRef.keydownEvents().pipe(\n       filter((event: KeyboardEvent) => {\n@@ -204,4 +204,4 @@ export class MenuTriggerForDirective implements OnDestroy {\n     const closeEvents = isContextMenu\n-      ? merge(detachments, escKey, menuClosed)\n-      : merge(detachments, escKey, this.overlayRef.backdropClick(), menuClosed);\n+      ? merge(detachments, keyEvents, menuClosed)\n+      : merge(detachments, keyEvents, this.overlayRef.backdropClick(), menuClosed);\n \n@@ -217,5 +217,5 @@ export class MenuTriggerForDirective implements OnDestroy {\n \n-        if (event instanceof KeyboardEvent && (event.key === \"Tab\" || event.key === \"Escape\")) {\n-          this.elementRef.nativeElement.focus();\n-        }\n+        // Move focus to the menu trigger, since any active menu items are about to be destroyed\n+        this.elementRef.nativeElement.focus();\n+\n         this.destroyMenu();\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Thomas Rittson","training-data":{"loc-added":"2","loc-deleted":"9","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"31796059+eliykat@users.noreply.github.com","commit-full-message":"","commit-date":"2025-05-07T01:23:18Z","current-rev":"df40954b61","filename":"clients/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts","previous-rev":"744c1b1b49","commit-title":"[PM-14613] Remove account deprovisioning feature flag (#14353)","language":"TypeScript","id":"867c26202ff84c026c234cf9b6ca131e28e7f5ea","model-score":0.77,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"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\nindex bc7dff3e70..5df2d7799d 100644\n--- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts\n+++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts\n@@ -154,6 +154,2 @@ export class MemberDialogComponent implements OnDestroy {\n \n-  protected accountDeprovisioningEnabled$: Observable<boolean> = this.configService.getFeatureFlag$(\n-    FeatureFlag.AccountDeprovisioning,\n-  );\n-\n   protected isExternalIdVisible$ = this.configService\n@@ -669,7 +665,5 @@ export class MemberDialogComponent implements OnDestroy {\n       this.deleteManagedMemberWarningService.warningAcknowledged(this.params.organizationId),\n-      this.accountDeprovisioningEnabled$,\n     ]).pipe(\n       map(\n-        ([organization, acknowledged, featureFlagEnabled]) =>\n-          featureFlagEnabled &&\n+        ([organization, acknowledged]) =>\n           organization.canManageUsers &&\n@@ -716,5 +710,4 @@ export class MemberDialogComponent implements OnDestroy {\n \n-    if (await firstValueFrom(this.accountDeprovisioningEnabled$)) {\n-      await this.deleteManagedMemberWarningService.acknowledgeWarning(this.params.organizationId);\n-    }\n+    await this.deleteManagedMemberWarningService.acknowledgeWarning(this.params.organizationId);\n+\n     this.close(MemberDialogResult.Deleted);\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Victoria League","training-data":{"loc-added":"5","loc-deleted":"8","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.096655465156704"},"author-email":"vleague@bitwarden.com","commit-full-message":"","commit-date":"2024-12-27T20:42:35Z","current-rev":"4f060d88fa","filename":"clients/apps/browser/src/popup/services/init.service.ts","previous-rev":"6d65ce9abd","commit-title":"[CL-509][PM-16190] Avoid double scrollbar appearing when default zoom is >100% (#12427)","language":"TypeScript","id":"aebbab9343e0124b2c28f2c63ae59e9dd802d12d","model-score":0.69,"author-id":null,"project-id":26215,"delta-file-score":0.28049663,"diff":"diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts\nindex 9e6471eaf2..2466143849 100644\n--- a/apps/browser/src/popup/services/init.service.ts\n+++ b/apps/browser/src/popup/services/init.service.ts\n@@ -1,3 +1,3 @@\n import { DOCUMENT } from \"@angular/common\";\n-import { Inject, Injectable } from \"@angular/core\";\n+import { inject, Inject, Injectable } from \"@angular/core\";\n \n@@ -12,4 +12,7 @@ import { BrowserApi } from \"../../platform/browser/browser-api\";\n import BrowserPopupUtils from \"../../platform/popup/browser-popup-utils\";\n+import { PopupSizeService } from \"../../platform/popup/layout/popup-size.service\";\n @Injectable()\n export class InitService {\n+  private sizeService = inject(PopupSizeService);\n+\n   constructor(\n@@ -30,9 +33,3 @@ export class InitService {\n \n-      if (!BrowserPopupUtils.inPopup(window)) {\n-        window.document.body.classList.add(\"body-full\");\n-      } else if (window.screen.availHeight < 600) {\n-        window.document.body.classList.add(\"body-xs\");\n-      } else if (window.screen.availHeight <= 800) {\n-        window.document.body.classList.add(\"body-sm\");\n-      }\n+      await this.sizeService.init();\n \n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Todd Martin","training-data":{"loc-added":"2","loc-deleted":"13","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.71","delta-n-functions":"0","current-file-score":"9.842730062691357"},"author-email":"106564991+trmartin4@users.noreply.github.com","commit-full-message":"Co-authored-by: Matt Bishop <mbishop@bitwarden.com>","commit-date":"2024-11-15T17:34:02Z","current-rev":"0308e6e180","filename":"clients/libs/auth/src/angular/login/login.component.ts","previous-rev":"d55c8712ac","commit-title":"Remove showPasswordless conditionals (#11928)","language":"TypeScript","id":"6bf6a9814c081d735966050dfac81c4bf78e9bd7","model-score":0.64,"author-id":null,"project-id":26215,"delta-file-score":0.66286343,"diff":"diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts\nindex 0193e4c403..9f86de8372 100644\n--- a/libs/auth/src/angular/login/login.component.ts\n+++ b/libs/auth/src/angular/login/login.component.ts\n@@ -106,8 +106,2 @@ export class LoginComponent implements OnInit, OnDestroy {\n \n-  /**\n-   * LoginViaAuthRequestSupported is a boolean that determines if we show the Login with device button.\n-   * An AuthRequest is the mechanism that allows users to login to the client via a device that is already logged in.\n-   */\n-  loginViaAuthRequestSupported = false;\n-\n   // Web properties\n@@ -146,3 +140,2 @@ export class LoginComponent implements OnInit, OnDestroy {\n     this.clientType = this.platformUtilsService.getClientType();\n-    this.loginViaAuthRequestSupported = this.loginComponentService.isLoginViaAuthRequestSupported();\n   }\n@@ -404,6 +397,4 @@ export class LoginComponent implements OnInit, OnDestroy {\n \n-      if (this.loginViaAuthRequestSupported) {\n-        // Reset known device state when going back to email entry if it is supported\n-        this.isKnownDevice = false;\n-      }\n+      // Reset known device state when going back to email entry if it is supported\n+      this.isKnownDevice = false;\n     } else if (this.loginUiState === LoginUiState.MASTER_PASSWORD_ENTRY) {\n@@ -428,5 +419,4 @@ export class LoginComponent implements OnInit, OnDestroy {\n \n-      if (this.loginViaAuthRequestSupported) {\n-        await this.getKnownDevice(this.emailFormControl.value);\n-      }\n+      // Check to see if the device is known so we can show the Login with Device option\n+      await this.getKnownDevice(this.emailFormControl.value);\n     }\n@@ -582,5 +572,4 @@ export class LoginComponent implements OnInit, OnDestroy {\n \n-    if (this.loginViaAuthRequestSupported) {\n-      await this.getKnownDevice(this.emailFormControl.value);\n-    }\n+    // Check to see if the device is known so that we can show the Login with Device option\n+    await this.getKnownDevice(this.emailFormControl.value);\n \n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Daniel James Smith","training-data":{"loc-added":"8","loc-deleted":"15","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"2670567+djsmith85@users.noreply.github.com","commit-full-message":"`safeGetString` takes a `string` or `EncString` and return the appropiate value based on it's type\r\n\r\nCo-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>","commit-date":"2024-04-24T21:41:35Z","current-rev":"dba910d0b9","filename":"clients/libs/common/src/models/export/card.export.ts","previous-rev":"a6755f5f20","commit-title":"Create and use `safeGetString()` instead of `instanceof` checks to determine type (#8906)","language":"TypeScript","id":"267ba674f9dcad4c4df46bae64873f6dd7429d73","model-score":0.63,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/libs/common/src/models/export/card.export.ts b/libs/common/src/models/export/card.export.ts\nindex 55bb3a7be1..151b447e86 100644\n--- a/libs/common/src/models/export/card.export.ts\n+++ b/libs/common/src/models/export/card.export.ts\n@@ -4,2 +4,4 @@ import { CardView } from \"../../vault/models/view/card.view\";\n \n+import { safeGetString } from \"./utils\";\n+\n export class CardExport {\n@@ -48,17 +50,8 @@ export class CardExport {\n \n-    if (o instanceof CardView) {\n-      this.cardholderName = o.cardholderName;\n-      this.brand = o.brand;\n-      this.number = o.number;\n-      this.expMonth = o.expMonth;\n-      this.expYear = o.expYear;\n-      this.code = o.code;\n-    } else {\n-      this.cardholderName = o.cardholderName?.encryptedString;\n-      this.brand = o.brand?.encryptedString;\n-      this.number = o.number?.encryptedString;\n-      this.expMonth = o.expMonth?.encryptedString;\n-      this.expYear = o.expYear?.encryptedString;\n-      this.code = o.code?.encryptedString;\n-    }\n+    this.cardholderName = safeGetString(o.cardholderName);\n+    this.brand = safeGetString(o.brand);\n+    this.number = safeGetString(o.number);\n+    this.expMonth = safeGetString(o.expMonth);\n+    this.expYear = safeGetString(o.expYear);\n+    this.code = safeGetString(o.code);\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vincent Salucci","training-data":{"loc-added":"7","loc-deleted":"21","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"26154748+vincentsalucci@users.noreply.github.com","commit-full-message":"* chore: remove fc v1 from org.canEditAnyCollection and update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from collectionView.canEdit and update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from organization.canEditAllCiphers and update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from canDeleteAnyCollection, collection views, update callers, refs PM-10294\r\n\r\n* chore: remove fc v1 from canEditUser/GroupAccess, refs PM-10294\r\n\r\n* chore: remove fc v1 from canViewCollectionInfo, refs PM-10294\r\n\r\n* chore: remove fc v1 from account component, refs PM-10294\r\n\r\n* fix: remove fc v1 from collections component, refs PM-10294\r\n\r\n* fix: update vault-items component, refs PM-10294\r\n\r\n* fix: remove fc v1 from collection-dialog and collections components, refs PM-10294\r\n\r\n* chore: remove ConfigService from group-add-edit and account components, refs PM-10294\r\n\r\n* chore: change canEditAnyCollection to getter and update callers, refs PM-10294\r\n\r\n* chore: change canEditUnmanagedCollections to getter and update callers, refs PM-10294\r\n\r\n* chore: change canDeleteAnyCollection to getter and update callers, refs PM-10294\r\n\r\n* chore: remove deprecated observable and update comments with v1, refs PM-10294\r\n\r\n* chore: remove ununsed ConfigService from collection-dialog component, refs PM-10294\r\n\r\n* chore: remove final fc v1 ref for vault-collection-row, refs PM-10294","commit-date":"2024-08-13T15:45:41Z","current-rev":"471dd3bd7b","filename":"clients/libs/common/src/admin-console/models/domain/organization.ts","previous-rev":"43da67ee51","commit-title":"[PM-10294] Remove FC v1 from Clients (#10422)","language":"TypeScript","id":"7f727af1ed624cfdaf8cc2d8a87b9290f138534c","model-score":0.59,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts\nindex dcffe6f158..490c799ad1 100644\n--- a/libs/common/src/admin-console/models/domain/organization.ts\n+++ b/libs/common/src/admin-console/models/domain/organization.ts\n@@ -170,9 +170,4 @@ export class Organization {\n \n-  canEditAnyCollection(flexibleCollectionsV1Enabled: boolean) {\n-    if (!flexibleCollectionsV1Enabled) {\n-      // Pre-Flexible Collections v1 logic\n-      return this.isAdmin || this.permissions.editAnyCollection;\n-    }\n-\n-    // Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins\n+  get canEditAnyCollection() {\n+    // The allowAdminAccessToAllCollectionItems flag can restrict admins\n     // Providers and custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag\n@@ -185,3 +180,3 @@ export class Organization {\n \n-  canEditUnmanagedCollections() {\n+  get canEditUnmanagedCollections() {\n     // Any admin or custom user with editAnyCollection permission can edit unmanaged collections\n@@ -205,11 +200,3 @@ export class Organization {\n \n-  canEditAllCiphers(\n-    flexibleCollectionsV1Enabled: boolean,\n-    restrictProviderAccessFlagEnabled: boolean,\n-  ) {\n-    // Before Flexible Collections V1, any admin or anyone with editAnyCollection permission could edit all ciphers\n-    if (!flexibleCollectionsV1Enabled) {\n-      return this.isAdmin || this.permissions.editAnyCollection;\n-    }\n-\n+  canEditAllCiphers(restrictProviderAccessFlagEnabled: boolean) {\n     // Providers can access items until the restrictProviderAccess flag is enabled\n@@ -221,3 +208,3 @@ export class Organization {\n \n-    // Post Flexible Collections V1, the allowAdminAccessToAllCollectionItems flag can restrict admins\n+    // The allowAdminAccessToAllCollectionItems flag can restrict admins\n     // Custom users with canEditAnyCollection are not affected by allowAdminAccessToAllCollectionItems flag\n@@ -231,6 +218,5 @@ export class Organization {\n   /**\n-   * @param flexibleCollectionsV1Enabled - Whether or not the V1 Flexible Collection feature flag is enabled\n    * @returns True if the user can delete any collection\n    */\n-  canDeleteAnyCollection(flexibleCollectionsV1Enabled: boolean) {\n+  get canDeleteAnyCollection() {\n     // Providers and Users with DeleteAnyCollection permission can always delete collections\n@@ -242,3 +228,3 @@ export class Organization {\n     // Using explicit type checks because provider users are handled above and this mimics the server's permission checks closely\n-    if (!flexibleCollectionsV1Enabled || this.allowAdminAccessToAllCollectionItems) {\n+    if (this.allowAdminAccessToAllCollectionItems) {\n       return this.type == OrganizationUserType.Owner || this.type == OrganizationUserType.Admin;\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"cd-bitwarden","training-data":{"loc-added":"1","loc-deleted":"12","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"106776772+cd-bitwarden@users.noreply.github.com","commit-full-message":"* Removing feature flag\r\n\r\n* Removing flag from feature-flag.enum.ts\r\n\r\n* suggested changes\r\n\r\n* prettier\r\n\r\n* fixing merge conflict issue\r\n\r\n* Removing unused code\r\n\r\n* suggested change from Gbubemi\r\n\r\n* Adding back merge conflict code\r\n\r\n* fixing prettier styling","commit-date":"2024-10-02T16:55:54Z","current-rev":"a23991a64b","filename":"clients/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts","previous-rev":"21a4b48eca","commit-title":"[pm-10995] feature flag removal (#11000)","language":"TypeScript","id":"b5e9bd19d81d99af873cf9b5662ab33645ff545f","model-score":0.58,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts\nindex 72d6a57aad..3b44662ecd 100644\n--- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts\n+++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts\n@@ -37,3 +37,2 @@ export class VaultCipherRowComponent implements OnInit {\n   @Input() canEditCipher: boolean;\n-  @Input() vaultBulkManagementActionEnabled: boolean;\n \n@@ -102,13 +101,11 @@ export class VaultCipherRowComponent implements OnInit {\n   protected get disableMenu() {\n-    return (\n-      !(\n-        this.isNotDeletedLoginCipher ||\n-        this.showCopyPassword ||\n-        this.showCopyTotp ||\n-        this.showLaunchUri ||\n-        this.showAttachments ||\n-        this.showClone ||\n-        this.canEditCipher ||\n-        this.cipher.isDeleted\n-      ) && this.vaultBulkManagementActionEnabled\n+    return !(\n+      this.isNotDeletedLoginCipher ||\n+      this.showCopyPassword ||\n+      this.showCopyTotp ||\n+      this.showLaunchUri ||\n+      this.showAttachments ||\n+      this.showClone ||\n+      this.canEditCipher ||\n+      this.cipher.isDeleted\n     );\n@@ -124,10 +121,2 @@ export class VaultCipherRowComponent implements OnInit {\n \n-  protected moveToOrganization() {\n-    this.onEvent.emit({ type: \"moveToOrganization\", items: [this.cipher] });\n-  }\n-\n-  protected editCollections() {\n-    this.onEvent.emit({ type: \"viewCipherCollections\", item: this.cipher });\n-  }\n-\n   protected events() {\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Nick Krantz","training-data":{"loc-added":"8","loc-deleted":"0","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.096655465156704"},"author-email":"125900171+nick-livefront@users.noreply.github.com","commit-full-message":"* add exclamation badge for at risk passwords on tab\n\n* add berry icon for the badge when pending tasks are present\n\n* remove integration wtih autofill for pending task badge\n\n* add ability to override Never match strategy\n- This is helpful for non-autofill purposes but cipher matching is still needed. This will default to the domain.\n\n* add at-risk-cipher badge updater service\n\n* Revert \"add exclamation badge for at risk passwords on tab\"\n\nThis reverts commit a9643c03d5ff812a88d554b4a4bcb13f0d5444f0.\n\n* remove nullish-coalescing\n\n* ensure that all user related observables use the same user.id\n\n---------\n\nCo-authored-by: Shane Melton <smelton@bitwarden.com>","commit-date":"2025-09-02T20:09:20Z","current-rev":"5967cf0539","filename":"clients/libs/common/src/vault/models/view/login-uri.view.ts","previous-rev":"a4fca832f3","commit-title":"[PM-14571] At Risk Passwords - Badge Update (#15983)","language":"TypeScript","id":"a1310fb3083672373a1aa86df05ae2a0c330bbb2","model-score":0.57,"author-id":null,"project-id":26215,"delta-file-score":0.28049663,"diff":"diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts\nindex 38cd517e54..49ac9c6278 100644\n--- a/libs/common/src/vault/models/view/login-uri.view.ts\n+++ b/libs/common/src/vault/models/view/login-uri.view.ts\n@@ -144,2 +144,4 @@ export class LoginUriView implements View {\n     defaultUriMatch: UriMatchStrategySetting = null,\n+    /** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */\n+    overrideNeverMatchStrategy?: true,\n   ): boolean {\n@@ -152,2 +154,8 @@ export class LoginUriView implements View {\n \n+    // Override the match strategy with `Domain` when it is `Never` and `overrideNeverMatchStrategy` is true.\n+    // This is useful in scenarios when the cipher should be matched to rely other information other than autofill.\n+    if (overrideNeverMatchStrategy && matchType === UriMatchStrategy.Never) {\n+      matchType = UriMatchStrategy.Domain;\n+    }\n+\n     const targetDomain = Utils.getDomain(targetUri);\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Maciej Zieniuk","training-data":{"loc-added":"17","loc-deleted":"19","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.842730062691357"},"author-email":"167752252+mzieniukbw@users.noreply.github.com","commit-full-message":"* enforce session timeout policy\n\n* better angular validation\n\n* lint fix\n\n* missing switch break\n\n* fallback when timeout not supported with highest available timeout\n\n* failing unit tests\n\n* incorrect policy message\n\n* vault timeout type adjustments\n\n* fallback to \"on browser refresh\" for browser, when policy is set to \"on system locked\", but not available (Safari)\n\n* docs, naming improvements\n\n* fallback for current user session timeout to \"on refresh\", when policy is set to \"on system locked\", but not available.\n\n* don't display policy message when the policy does not affect available timeout options\n\n* 8 hours default when changing from non-numeric timeout to Custom.\n\n* failing unit test\n\n* missing locales, changing functions access to private, docs\n\n* removal of redundant magic number\n\n* missing await\n\n* await once for available timeout options\n\n* adjusted messaging\n\n* unit test coverage\n\n* vault timeout numeric module exports\n\n* unit test coverage","commit-date":"2025-12-05T13:55:59Z","current-rev":"bbea11388a","filename":"clients/bitwarden_license/bit-web/src/app/key-management/policies/session-timeout.component.ts","previous-rev":"c036ffd775","commit-title":"[PM-26057] Enforce session timeout policy (#17424)","language":"TypeScript","id":"5b3318d86300afeff3dd125f0d299dca4e780cb4","model-score":0.56,"author-id":null,"project-id":26215,"delta-file-score":0.3063433,"diff":"diff --git a/bitwarden_license/bit-web/src/app/key-management/policies/session-timeout.component.ts b/bitwarden_license/bit-web/src/app/key-management/policies/session-timeout.component.ts\nindex 9c6129f64d..fd541d3040 100644\n--- a/bitwarden_license/bit-web/src/app/key-management/policies/session-timeout.component.ts\n+++ b/bitwarden_license/bit-web/src/app/key-management/policies/session-timeout.component.ts\n@@ -12,2 +12,7 @@ import {\n import { PolicyType } from \"@bitwarden/common/admin-console/enums\";\n+import {\n+  MaximumSessionTimeoutPolicyData,\n+  SessionTimeoutAction,\n+  SessionTimeoutType,\n+} from \"@bitwarden/common/key-management/session-timeout\";\n import { VaultTimeoutAction } from \"@bitwarden/common/key-management/vault-timeout\";\n@@ -16,4 +21,4 @@ import { DialogService } from \"@bitwarden/components\";\n import {\n-  BasePolicyEditDefinition,\n   BasePolicyEditComponent,\n+  BasePolicyEditDefinition,\n } from \"@bitwarden/web-vault/app/admin-console/organizations/policies\";\n@@ -23,11 +28,2 @@ import { SessionTimeoutConfirmationNeverComponent } from \"./session-timeout-conf\n \n-export type SessionTimeoutAction = null | \"lock\" | \"logOut\";\n-export type SessionTimeoutType =\n-  | null\n-  | \"never\"\n-  | \"onAppRestart\"\n-  | \"onSystemLock\"\n-  | \"immediately\"\n-  | \"custom\";\n-\n export class SessionTimeoutPolicy extends BasePolicyEditDefinition {\n@@ -52,5 +48,2 @@ export class SessionTimeoutPolicyComponent\n {\n-  private destroy$ = new Subject<void>();\n-  private lastConfirmedType$ = new BehaviorSubject<SessionTimeoutType>(null);\n-\n   actionOptions: { name: string; value: SessionTimeoutAction }[];\n@@ -76,2 +69,5 @@ export class SessionTimeoutPolicyComponent\n \n+  private destroy$ = new Subject<void>();\n+  private lastConfirmedType$ = new BehaviorSubject<SessionTimeoutType>(null);\n+\n   constructor(\n@@ -125,8 +121,6 @@ export class SessionTimeoutPolicyComponent\n   protected override loadData() {\n-    const minutes: number | null = this.policyResponse?.data?.minutes ?? null;\n-    const action: SessionTimeoutAction =\n-      this.policyResponse?.data?.action ?? (null satisfies SessionTimeoutAction);\n+    const minutes: number | null = this.policyData?.minutes ?? null;\n+    const action: SessionTimeoutAction = this.policyData?.action ?? null;\n     // For backward compatibility, the \"type\" field might not exist, hence we initialize it based on the presence of \"minutes\"\n-    const type: SessionTimeoutType =\n-      this.policyResponse?.data?.type ?? ((minutes ? \"custom\" : null) satisfies SessionTimeoutType);\n+    const type: SessionTimeoutType = this.policyData?.type ?? (minutes ? \"custom\" : null);\n \n@@ -167,3 +161,7 @@ export class SessionTimeoutPolicyComponent\n       action: this.data.value.action,\n-    };\n+    } satisfies MaximumSessionTimeoutPolicyData;\n+  }\n+\n+  private get policyData(): MaximumSessionTimeoutPolicyData | null {\n+    return this.policyResponse?.data ?? null;\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Leslie Tilton","training-data":{"loc-added":"10","loc-deleted":"21","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"23057410+Banrion@users.noreply.github.com","commit-full-message":"* feat(access-intelligence): hookup trend widget and add missing properties to summary\n\n* Fix graph looking at non critical numbers\n\n* Swap at risk members card and password change metric widget","commit-date":"2026-03-24T19:30:44Z","current-rev":"3b5b784d10","filename":"clients/bitwarden_license/bit-common/src/dirt/access-intelligence/models/api/access-report-summary.api.ts","previous-rev":"8abdd081b4","commit-title":"[PM-32057] Wire up Trend Widget in Access Intelligence Activity (#19664)","language":"TypeScript","id":"39fd9854b0cc1368983b8a923a0d29d6c2f130ed","model-score":0.54,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/bitwarden_license/bit-common/src/dirt/access-intelligence/models/api/access-report-summary.api.ts b/bitwarden_license/bit-common/src/dirt/access-intelligence/models/api/access-report-summary.api.ts\nindex c64d451414..9d0c437642 100644\n--- a/bitwarden_license/bit-common/src/dirt/access-intelligence/models/api/access-report-summary.api.ts\n+++ b/bitwarden_license/bit-common/src/dirt/access-intelligence/models/api/access-report-summary.api.ts\n@@ -10,3 +10,3 @@ import { AccessReportSummaryView } from \"../view/access-report-summary.view\";\n /**\n- * Converts an AccessReportSummary API response\n+ * API response model for an encrypted access report summary entry.\n  *\n@@ -14,13 +14,9 @@ import { AccessReportSummaryView } from \"../view/access-report-summary.view\";\n  * - See {@link AccessReportSummaryData} for data model\n- * - See {@link AccessReportSummaryView} from View Model\n+ * - See {@link AccessReportSummaryView} for view model\n  */\n export class AccessReportSummaryApi extends BaseResponse {\n-  totalMemberCount: number = 0;\n-  totalApplicationCount: number = 0;\n-  totalAtRiskMemberCount: number = 0;\n-  totalAtRiskApplicationCount: number = 0;\n-  totalCriticalApplicationCount: number = 0;\n-  totalCriticalMemberCount: number = 0;\n-  totalCriticalAtRiskMemberCount: number = 0;\n-  totalCriticalAtRiskApplicationCount: number = 0;\n+  organizationId: string = \"\";\n+  encryptedData: string = \"\";\n+  encryptionKey: string = \"\";\n+  date: string = \"\";\n \n@@ -29,13 +25,6 @@ export class AccessReportSummaryApi extends BaseResponse {\n \n-    this.totalMemberCount = this.getResponseProperty(\"totalMemberCount\") || 0;\n-    this.totalApplicationCount = this.getResponseProperty(\"totalApplicationCount\") || 0;\n-    this.totalAtRiskMemberCount = this.getResponseProperty(\"totalAtRiskMemberCount\") || 0;\n-    this.totalAtRiskApplicationCount = this.getResponseProperty(\"totalAtRiskApplicationCount\") || 0;\n-    this.totalCriticalApplicationCount =\n-      this.getResponseProperty(\"totalCriticalApplicationCount\") || 0;\n-    this.totalCriticalMemberCount = this.getResponseProperty(\"totalCriticalMemberCount\") || 0;\n-    this.totalCriticalAtRiskMemberCount =\n-      this.getResponseProperty(\"totalCriticalAtRiskMemberCount\") || 0;\n-    this.totalCriticalAtRiskApplicationCount =\n-      this.getResponseProperty(\"totalCriticalAtRiskApplicationCount\") || 0;\n+    this.organizationId = this.getResponseProperty(\"OrganizationId\") ?? \"\";\n+    this.encryptedData = this.getResponseProperty(\"EncryptedData\") ?? \"\";\n+    this.encryptionKey = this.getResponseProperty(\"EncryptionKey\") ?? \"\";\n+    this.date = this.getResponseProperty(\"Date\") ?? \"\";\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Matt Gibson","training-data":{"loc-added":"1","loc-deleted":"23","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mgibson@bitwarden.com","commit-full-message":"* remove active account unlocked from state service\r\n\r\n* Remove status from account service `AccountInfo`\r\n\r\n* Fixup lingering usages of status\r\n\r\nFixup missed factories\r\n\r\n* Fixup account info usage\r\n\r\n* fixup CLI build\r\n\r\n* Fixup current account type\r\n\r\n* Add helper for all auth statuses to auth service\r\n\r\n* Fix tests\r\n\r\n* Uncomment mistakenly commented code\r\n\r\n* Rework logged out account exclusion tests\r\n\r\n* Correct test description\r\n\r\n* Avoid getters returning observables\r\n\r\n* fixup type","commit-date":"2024-04-12T07:25:45Z","current-rev":"8d698d9d84","filename":"clients/libs/common/src/auth/abstractions/account.service.ts","previous-rev":"c7ea35280d","commit-title":"[PM-7169][PM-5267] Remove auth status from account info (#8539)","language":"TypeScript","id":"9fb72a4d21dee7215bb08e2fc97940101822c66f","model-score":0.51,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/libs/common/src/auth/abstractions/account.service.ts b/libs/common/src/auth/abstractions/account.service.ts\nindex 4e2a462755..fa9ad36378 100644\n--- a/libs/common/src/auth/abstractions/account.service.ts\n+++ b/libs/common/src/auth/abstractions/account.service.ts\n@@ -3,3 +3,2 @@ import { Observable } from \"rxjs\";\n import { UserId } from \"../../types/guid\";\n-import { AuthenticationStatus } from \"../enums/authentication-status\";\n \n@@ -10,3 +9,2 @@ import { AuthenticationStatus } from \"../enums/authentication-status\";\n export type AccountInfo = {\n-  status: AuthenticationStatus;\n   email: string;\n@@ -16,3 +14,3 @@ export type AccountInfo = {\n export function accountInfoEqual(a: AccountInfo, b: AccountInfo) {\n-  return a?.status === b?.status && a?.email === b?.email && a?.name === b?.name;\n+  return a?.email === b?.email && a?.name === b?.name;\n }\n@@ -22,4 +20,2 @@ export abstract class AccountService {\n   activeAccount$: Observable<{ id: UserId | undefined } & AccountInfo>;\n-  accountLock$: Observable<UserId>;\n-  accountLogout$: Observable<UserId>;\n   /**\n@@ -42,20 +38,2 @@ export abstract class AccountService {\n   abstract setAccountEmail(userId: UserId, email: string): Promise<void>;\n-  /**\n-   * Updates the `accounts$` observable with the new account status.\n-   * Also emits the `accountLock$` or `accountLogout$` observable if the status is `Locked` or `LoggedOut` respectively.\n-   * @param userId\n-   * @param status\n-   */\n-  abstract setAccountStatus(userId: UserId, status: AuthenticationStatus): Promise<void>;\n-  /**\n-   * Updates the `accounts$` observable with the new account status if the current status is higher than the `maxStatus`.\n-   *\n-   * This method only downgrades status to the maximum value sent in, it will not increase authentication status.\n-   *\n-   * @example An account is transitioning from unlocked to logged out. If callbacks that set the status to locked occur\n-   * after it is updated to logged out, the account will be in the incorrect state.\n-   * @param userId The user id of the account to be updated.\n-   * @param maxStatus The new status of the account.\n-   */\n-  abstract setMaxAccountStatus(userId: UserId, maxStatus: AuthenticationStatus): Promise<void>;\n   /**\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Maximilian Power","training-data":{"loc-added":"7","loc-deleted":"13","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"8.545379580978913"},"author-email":"mpower@bitwarden.com","commit-full-message":"* updated strings","commit-date":"2025-11-17T16:50:39Z","current-rev":"16e4eb1dd0","filename":"clients/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts","previous-rev":"a2abbd09bf","commit-title":"updates strings (#17422)","language":"TypeScript","id":"142dfe5a49a579a1765e7ed0de40ae5a1871e606","model-score":0.51,"author-id":null,"project-id":26215,"delta-file-score":0.2613985,"diff":"diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts\nindex eddc26cbc7..9e6901572c 100644\n--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts\n+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts\n@@ -12,3 +12,3 @@ import { takeUntilDestroyed } from \"@angular/core/rxjs-interop\";\n import { ActivatedRoute, Router } from \"@angular/router\";\n-import { combineLatest, EMPTY, firstValueFrom } from \"rxjs\";\n+import { EMPTY, firstValueFrom } from \"rxjs\";\n import { distinctUntilChanged, map, tap } from \"rxjs/operators\";\n@@ -86,10 +86,7 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {\n \n-  // Empty state properties\n-  protected organizationName = \"\";\n-\n   // Empty state computed properties\n   protected emptyStateBenefits: [string, string][] = [\n-    [this.i18nService.t(\"benefit1Title\"), this.i18nService.t(\"benefit1Description\")],\n-    [this.i18nService.t(\"benefit2Title\"), this.i18nService.t(\"benefit2Description\")],\n-    [this.i18nService.t(\"benefit3Title\"), this.i18nService.t(\"benefit3Description\")],\n+    [this.i18nService.t(\"feature1Title\"), this.i18nService.t(\"feature1Description\")],\n+    [this.i18nService.t(\"feature2Title\"), this.i18nService.t(\"feature2Description\")],\n+    [this.i18nService.t(\"feature3Title\"), this.i18nService.t(\"feature3Description\")],\n   ];\n@@ -142,7 +139,7 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {\n \n-    // Combine report data, vault items check, organization details, and generation state\n+    // Subscribe to report data updates\n     // This declarative pattern ensures proper cleanup and prevents memory leaks\n-    combineLatest([this.dataService.enrichedReportData$, this.dataService.organizationDetails$])\n+    this.dataService.enrichedReportData$\n       .pipe(takeUntilDestroyed(this.destroyRef))\n-      .subscribe(([report, orgDetails]) => {\n+      .subscribe((report) => {\n         // Update report state\n@@ -150,5 +147,2 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {\n         this.dataLastUpdated = report?.creationDate ?? null;\n-\n-        // Update organization name\n-        this.organizationName = orgDetails?.organizationName ?? \"\";\n       });\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Cesar Gonzalez","training-data":{"loc-added":"40","loc-deleted":"20","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"cesar.a.gonzalezcs@gmail.com","commit-full-message":"* [PM-934] Autofill not working until page has been refreshed\r\n\r\n* [PM-934] Adjusting cleanup of the messages_handler script\r\n\r\n* [PM-934] Fixing small issue found within collection of page details\r\n\r\n* [PM-934] Addressing concenrs brought up during code review\r\n\r\n* [PM-934] Addressing concenrs brought up during code review\r\n\r\n* [PM-934] Addressing concenrs brought up during code review\r\n\r\n* [PM-934] Addressing concenrs brought up during code review\r\n\r\n* [PM-934] Applying re-set changes to the autofill overlay implementation on reset of the extension\r\n\r\n* [PM-934] Applying jest tests to added logic within AutofillOverlayContent service\r\n\r\n* [PM-934] Fixing typo present in tabs background listener\r\n\r\n* [PM-934] Finishing up jest tests for updated implementation\r\n\r\n* [PM-934] Incorporating methodology for ensuring the autofill overlay updates to reflect user settings within existing tabs\r\n\r\n* [PM-934] Refining implementation to ensure we do not unnecessarily re-inject content scripts when the autofill overlay settings change\r\n\r\n* [PM-934] Working through jest tests for added implementation details\r\n\r\n* [PM-934] Working through jest tests for added implementation details\r\n\r\n* [PM-934] Finalizing jest tests for implemented logic\r\n\r\n* [PM-5035] Refactoring method structure","commit-date":"2023-12-13T16:25:16Z","current-rev":"bf60711efe","filename":"clients/apps/browser/src/autofill/content/autofiller.ts","previous-rev":"7051f255ed","commit-title":"[PM-934] Autofill not working until page has been refreshed (#6826)","language":"TypeScript","id":"4187d50636f89a8190e2a4d9d4952944277ffb55","model-score":0.49,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/browser/src/autofill/content/autofiller.ts b/apps/browser/src/autofill/content/autofiller.ts\nindex 7f58e72c7d..c3a2f7f579 100644\n--- a/apps/browser/src/autofill/content/autofiller.ts\n+++ b/apps/browser/src/autofill/content/autofiller.ts\n@@ -1 +1,3 @@\n+import { getFromLocalStorage, setupExtensionDisconnectAction } from \"../utils\";\n+\n if (document.readyState === \"loading\") {\n@@ -10,23 +12,26 @@ function loadAutofiller() {\n   let delayFillTimeout: number;\n-\n-  const activeUserIdKey = \"activeUserId\";\n-  let activeUserId: string;\n-\n-  chrome.storage.local.get(activeUserIdKey, (obj: any) => {\n-    if (obj == null || obj[activeUserIdKey] == null) {\n-      return;\n+  let doFillInterval: NodeJS.Timeout;\n+  const handleExtensionDisconnect = () => {\n+    clearDoFillInterval();\n+    clearDelayFillTimeout();\n+  };\n+  const handleExtensionMessage = (message: any) => {\n+    if (message.command === \"fillForm\" && pageHref === message.url) {\n+      filledThisHref = true;\n     }\n-    activeUserId = obj[activeUserIdKey];\n-  });\n+  };\n \n-  chrome.storage.local.get(activeUserId, (obj: any) => {\n-    if (obj?.[activeUserId]?.settings?.enableAutoFillOnPageLoad === true) {\n-      setInterval(() => doFillIfNeeded(), 500);\n-    }\n-  });\n-  chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {\n-    if (msg.command === \"fillForm\" && pageHref === msg.url) {\n-      filledThisHref = true;\n+  setupExtensionEventListeners();\n+  triggerUserFillOnLoad();\n+\n+  async function triggerUserFillOnLoad() {\n+    const activeUserIdKey = \"activeUserId\";\n+    const userKeyStorage = await getFromLocalStorage(activeUserIdKey);\n+    const activeUserId = userKeyStorage[activeUserIdKey];\n+    const activeUserStorage = await getFromLocalStorage(activeUserId);\n+    if (activeUserStorage?.[activeUserId]?.settings?.enableAutoFillOnPageLoad === true) {\n+      clearDoFillInterval();\n+      doFillInterval = setInterval(() => doFillIfNeeded(), 500);\n     }\n-  });\n+  }\n \n@@ -38,5 +43,3 @@ function loadAutofiller() {\n         filledThisHref = false;\n-        if (delayFillTimeout != null) {\n-          window.clearTimeout(delayFillTimeout);\n-        }\n+        clearDelayFillTimeout();\n         delayFillTimeout = window.setTimeout(() => {\n@@ -57,2 +60,19 @@ function loadAutofiller() {\n   }\n+\n+  function clearDoFillInterval() {\n+    if (doFillInterval) {\n+      window.clearInterval(doFillInterval);\n+    }\n+  }\n+\n+  function clearDelayFillTimeout() {\n+    if (delayFillTimeout) {\n+      window.clearTimeout(delayFillTimeout);\n+    }\n+  }\n+\n+  function setupExtensionEventListeners() {\n+    setupExtensionDisconnectAction(handleExtensionDisconnect);\n+    chrome.runtime.onMessage.addListener(handleExtensionMessage);\n+  }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Bernd Schoolmann","training-data":{"loc-added":"1","loc-deleted":"7","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"8.545379580978913"},"author-email":"mail@quexten.com","commit-full-message":"","commit-date":"2025-12-03T12:11:03Z","current-rev":"a6100d8a0e","filename":"clients/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts","previous-rev":"cf416388d7","commit-title":"Replace webcrypto RSA with PureCrypto RSA (#17742)","language":"TypeScript","id":"aa52dee2cff8bc56a23a3801610da85efb1e2f3d","model-score":0.48,"author-id":null,"project-id":26215,"delta-file-score":0.2613985,"diff":"diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts\nindex 132bbc306c..a5da0c8238 100644\n--- a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts\n+++ b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts\n@@ -254,3 +254,2 @@ export class EncryptServiceImplementation implements EncryptService {\n \n-    let algorithm: \"sha1\" | \"sha256\";\n     switch (data.encryptionType) {\n@@ -258,7 +257,2 @@ export class EncryptServiceImplementation implements EncryptService {\n       case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:\n-        algorithm = \"sha1\";\n-        break;\n-      case EncryptionType.Rsa2048_OaepSha256_B64:\n-      case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:\n-        algorithm = \"sha256\";\n         break;\n@@ -272,3 +266,3 @@ export class EncryptServiceImplementation implements EncryptService {\n \n-    return this.cryptoFunctionService.rsaDecrypt(data.dataBytes, privateKey, algorithm);\n+    return this.cryptoFunctionService.rsaDecrypt(data.dataBytes, privateKey, \"sha1\");\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Matt Gibson","training-data":{"loc-added":"6","loc-deleted":"24","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"mgibson@bitwarden.com","commit-full-message":"* Remove getbgService for crypto service\r\n\r\n* Remove special authentication for state service\r\n\r\n* Use synced memory storage\r\n\r\npopup contexts use foreground, background contexts use background. Simple\r\n\r\n* Remove private mode warnings","commit-date":"2024-05-01T11:59:30Z","current-rev":"7e9ab6a15b","filename":"clients/apps/browser/src/background/main.background.ts","previous-rev":"b4631b0dd1","commit-title":"[PM-7807][PM-7617] [PM-6185] Firefox private mode out of experimentation (#8921)","language":"TypeScript","id":"8bc862cb03f6cf797314e2c09398413781e6f835","model-score":0.46,"author-id":null,"project-id":26215,"delta-file-score":0.8720495,"diff":"diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts\nindex adcb4a21ba..16b8c8beea 100644\n--- a/apps/browser/src/background/main.background.ts\n+++ b/apps/browser/src/background/main.background.ts\n@@ -111,3 +111,2 @@ import { FileUploadService } from \"@bitwarden/common/platform/services/file-uplo\n import { KeyGenerationService } from \"@bitwarden/common/platform/services/key-generation.service\";\n-import { MemoryStorageService } from \"@bitwarden/common/platform/services/memory-storage.service\";\n import { MigrationBuilderService } from \"@bitwarden/common/platform/services/migration-builder.service\";\n@@ -358,6 +357,3 @@ export default class MainBackground {\n \n-  constructor(\n-    public isPrivateMode: boolean = false,\n-    public popupOnlyContext: boolean = false,\n-  ) {\n+  constructor(public popupOnlyContext: boolean = false) {\n     // Services\n@@ -445,6 +441,10 @@ export default class MainBackground {\n       ? new BrowserMemoryStorageService() // mv3 stores to storage.session\n-      : new BackgroundMemoryStorageService(); // mv2 stores to memory\n+      : popupOnlyContext\n+        ? new ForegroundMemoryStorageService()\n+        : new BackgroundMemoryStorageService(); // mv2 stores to memory\n     this.memoryStorageService = BrowserApi.isManifestVersion(3)\n       ? this.memoryStorageForStateProviders // manifest v3 can reuse the same storage. They are split for v2 due to lacking a good sync mechanism, which isn't true for v3\n-      : new MemoryStorageService();\n+      : popupOnlyContext\n+        ? new ForegroundMemoryStorageService()\n+        : new BackgroundMemoryStorageService();\n     this.largeObjectMemoryStorageForStateProviders = BrowserApi.isManifestVersion(3)\n@@ -1111,23 +1111,5 @@ export default class MainBackground {\n \n-    if (this.platformUtilsService.isFirefox() && !this.isPrivateMode) {\n-      // Set Private Mode windows to the default icon - they do not share state with the background page\n-      const privateWindows = await BrowserApi.getPrivateModeWindows();\n-      privateWindows.forEach(async (win) => {\n-        await new UpdateBadge(self).setBadgeIcon(\"\", win.id);\n-      });\n-\n-      // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.\n-      // eslint-disable-next-line @typescript-eslint/no-floating-promises\n-      BrowserApi.onWindowCreated(async (win) => {\n-        if (win.incognito) {\n-          await new UpdateBadge(self).setBadgeIcon(\"\", win.id);\n-        }\n-      });\n-    }\n-\n     return new Promise<void>((resolve) => {\n       setTimeout(async () => {\n-        if (!this.isPrivateMode) {\n-          await this.refreshBadge();\n-        }\n+        await this.refreshBadge();\n         await this.fullSync(true);\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Leslie Tilton","training-data":{"loc-added":"7","loc-deleted":"33","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"23057410+Banrion@users.noreply.github.com","commit-full-message":"* Update type guard for cipher ids on reports\n\n* Update report model cipher id type and mock data\n\n* Update security tasks api service to have copied getAllTasks function from the vault team\n\n* Expose critical application at risk cipher ids\n\n* Update cipher id type in report service. Update all activities service to move task function to task service\n\n* Update module\n\n* Update organization id sharing through components instead of multiple route fetchings\n\n* Update view type of password change widget. Update variables to be signals. Refactor logic for calculations based on individual tasks\n\n* Update usage of request password change function\n\n* Update security tasks service to manage tasks\n\n* Remove unused variable\n\n* Alphabetized functions, added documentation. Removed injectable decorator\n\n* Alphabetize constructor params for password health service\n\n* Update providers\n\n* Address NaN case on percentage. Address obsolete type casting to CipherID and any other claude comments\n\n* Fix dependency array in test case","commit-date":"2025-11-07T18:04:05Z","current-rev":"ec07a5391a","filename":"clients/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts","previous-rev":"4f9ae78598","commit-title":"[PM-27762] Activity Tab - Password Change Progress - Assign tasks for new passwords  (#17268)","language":"TypeScript","id":"bb9d80f8967efc0e9dcb92f2d46a3821c16e1d50","model-score":0.44,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts\nindex 8a1a90245b..e415fbf9ad 100644\n--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts\n+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts\n@@ -10,3 +10,3 @@ import {\n import { takeUntilDestroyed } from \"@angular/core/rxjs-interop\";\n-import { from, switchMap } from \"rxjs\";\n+import { from, switchMap, take } from \"rxjs\";\n \n@@ -14,4 +14,2 @@ import {\n   ApplicationHealthReportDetail,\n-  ApplicationHealthReportDetailEnriched,\n-  OrganizationReportApplication,\n   RiskInsightsDataService,\n@@ -211,36 +209,12 @@ export class NewApplicationsDialogComponent {\n \n-    // Create updated organization report application types with new review date\n-    // and critical marking based on selected applications\n-    const newReviewDate = new Date();\n-    const updatedApplications: OrganizationReportApplication[] =\n-      this.dialogParams.newApplications.map((app) => ({\n-        applicationName: app.applicationName,\n-        isCritical: this.selectedApplications().has(app.applicationName),\n-        reviewedDate: newReviewDate,\n-      }));\n-\n     // Save the application review dates and critical markings\n-    this.dataService\n-      .saveApplicationReviewStatus(updatedApplications)\n+    this.dataService.criticalApplicationAtRiskCipherIds$\n       .pipe(\n-        takeUntilDestroyed(this.destroyRef),\n-        switchMap((updatedState) => {\n-          // After initial save is complete, created the assigned tasks\n-          // for at risk passwords\n-          const updatedStateApplicationData = updatedState?.data?.applicationData || [];\n-          // Manual enrich for type matching\n-          // TODO Consolidate in model updates\n-          const manualEnrichedApplications =\n-            updatedState?.data?.reportData.map(\n-              (application): ApplicationHealthReportDetailEnriched => ({\n-                ...application,\n-                isMarkedAsCritical: updatedStateApplicationData.some(\n-                  (a) => a.applicationName == application.applicationName && a.isCritical,\n-                ),\n-              }),\n-            ) || [];\n+        takeUntilDestroyed(this.destroyRef), // Satisfy eslint rule\n+        take(1), // Handle unsubscribe for one off operation\n+        switchMap((criticalApplicationAtRiskCipherIds) => {\n           return from(\n-            this.accessIntelligenceSecurityTasksService.assignTasks(\n+            this.accessIntelligenceSecurityTasksService.requestPasswordChangeForCriticalApplications(\n               this.dialogParams.organizationId,\n-              manualEnrichedApplications,\n+              criticalApplicationAtRiskCipherIds,\n             ),\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vincent Salucci","training-data":{"loc-added":"4","loc-deleted":"47","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"26154748+vincentsalucci@users.noreply.github.com","commit-full-message":"* chore: organization.ts, remove refs to flexibleCollections and isManager, refs AC-2647\r\n\r\n* chore: clean up callers of removed methods from organization.ts, refs AC-2647\r\n\r\n* chore: access-selector, remove fc input and update permissionList param, refs AC-2647\r\n\r\n* chore: update permissionList caller, update group-add-edit fc refs, and remove accessAll, refs AC-2647\r\n\r\n* chore: update member-dialog fc callers, refs AC-2647\r\n\r\n* chore: update bulk-collections-dialog fc callers, refs AC-2647\r\n\r\n* chore: update collection-dialog fc callers, refs AC-2647\r\n\r\n* chore: update simple fc caller to misc files, refs AC-2647\r\n\r\n* chore: update member-dialog fc callers, refs AC-2647\r\n\r\n* chore: remove accessAll references and update callers, refs AC-2647\r\n\r\n* chore: update comment to specify v1 usage, refs AC-2647\r\n\r\n* chore: remove unused message keys and code calls to use those messages, refs AC-2647\r\n\r\n* chore: remove readonly false from access-selector model map function, refs AC-2647","commit-date":"2024-06-10T16:59:20Z","current-rev":"b169207b74","filename":"clients/libs/common/src/admin-console/models/domain/organization.ts","previous-rev":"19f2d2aefc","commit-title":"[AC-2647] Remove Flexible Collections MVP code (#9518)","language":"TypeScript","id":"9625cee0d6bc9023b5de4cc43e591e0455be55f2","model-score":0.43,"author-id":null,"project-id":26215,"delta-file-score":0.61278176,"diff":"diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts\nindex b68de7ac08..2632a16da0 100644\n--- a/libs/common/src/admin-console/models/domain/organization.ts\n+++ b/libs/common/src/admin-console/models/domain/organization.ts\n@@ -144,12 +144,2 @@ export class Organization {\n \n-  /**\n-   * Whether a user has Manager permissions or greater\n-   *\n-   * @deprecated\n-   * This is deprecated with the introduction of Flexible Collections.\n-   */\n-  get isManager() {\n-    return this.type === OrganizationUserType.Manager || this.isAdmin;\n-  }\n-\n   /**\n@@ -181,11 +171,5 @@ export class Organization {\n   get canCreateNewCollections() {\n-    if (this.flexibleCollections) {\n-      return (\n-        !this.limitCollectionCreationDeletion ||\n-        this.isAdmin ||\n-        this.permissions.createNewCollections\n-      );\n-    }\n-\n-    return this.isManager || this.permissions.createNewCollections;\n+    return (\n+      !this.limitCollectionCreationDeletion || this.isAdmin || this.permissions.createNewCollections\n+    );\n   }\n@@ -193,3 +177,3 @@ export class Organization {\n   canEditAnyCollection(flexibleCollectionsV1Enabled: boolean) {\n-    if (!this.flexibleCollections || !flexibleCollectionsV1Enabled) {\n+    if (!flexibleCollectionsV1Enabled) {\n       // Pre-Flexible Collections v1 logic\n@@ -223,4 +207,4 @@ export class Organization {\n   ) {\n-    // Before Flexible Collections, any admin or anyone with editAnyCollection permission could edit all ciphers\n-    if (!this.flexibleCollections || !flexibleCollectionsV1Enabled || !this.flexibleCollections) {\n+    // Before Flexible Collections V1, any admin or anyone with editAnyCollection permission could edit all ciphers\n+    if (!flexibleCollectionsV1Enabled) {\n       return this.isAdmin || this.permissions.editAnyCollection;\n@@ -271,29 +255,2 @@ export class Organization {\n \n-  /**\n-   * @deprecated\n-   * This is deprecated with the introduction of Flexible Collections.\n-   * This will always return false if FlexibleCollections flag is on.\n-   */\n-  get canEditAssignedCollections() {\n-    return this.isManager || this.permissions.editAssignedCollections;\n-  }\n-\n-  /**\n-   * @deprecated\n-   * This is deprecated with the introduction of Flexible Collections.\n-   * This will always return false if FlexibleCollections flag is on.\n-   */\n-  get canDeleteAssignedCollections() {\n-    return this.isManager || this.permissions.deleteAssignedCollections;\n-  }\n-\n-  /**\n-   * @deprecated\n-   * This is deprecated with the introduction of Flexible Collections.\n-   * This will always return false if FlexibleCollections flag is on.\n-   */\n-  get canViewAssignedCollections() {\n-    return this.canDeleteAssignedCollections || this.canEditAssignedCollections;\n-  }\n-\n   get canManageGroups() {\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Andreas Coroiu","training-data":{"loc-added":"18","loc-deleted":"57","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"acoroiu@bitwarden.com","commit-full-message":"* wip\n\n* feat: add dynamic states\n\n* feat: re-implement badge service with dynamic state functions\n\n* feat: completely remove old static states\n\n* feat: debounce calls to badge api per tab\n\n* feat: use group-by to avoid re-setting all tabs on 1 tab change\n\n* feat: simplify autofill badge updater\n\n* feat: add hanging function test\n\n* chore: clean up badge service\n\n* feat: simplify private updateBadge\n\n* feat: remove unnecessary Set usage\n\n* fix: tests that broke after setState rename\n\n* chore: clean up badge api","commit-date":"2025-10-03T07:01:49Z","current-rev":"2ddf1c34b2","filename":"clients/apps/browser/src/autofill/services/autofill-badge-updater.service.ts","previous-rev":"fdf47ffe3b","commit-title":"[PM-25488] Badge stays after lock when using pin (#16436)","language":"TypeScript","id":"f022b9a7d8508802c62cce0e61055d10b3e1bf86","model-score":0.42,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/browser/src/autofill/services/autofill-badge-updater.service.ts b/apps/browser/src/autofill/services/autofill-badge-updater.service.ts\nindex 06ddf16c8a..382c9efa7f 100644\n--- a/apps/browser/src/autofill/services/autofill-badge-updater.service.ts\n+++ b/apps/browser/src/autofill/services/autofill-badge-updater.service.ts\n@@ -1,2 +1,2 @@\n-import { combineLatest, distinctUntilChanged, mergeMap, of, switchMap, withLatestFrom } from \"rxjs\";\n+import { combineLatest, delay, distinctUntilChanged, mergeMap, of, switchMap } from \"rxjs\";\n \n@@ -12,3 +12,3 @@ import { BadgeStatePriority } from \"../../platform/badge/priority\";\n \n-const StateName = (tabId: number) => `autofill-badge-${tabId}`;\n+const StateName = \"autofill-badge-updater\";\n \n@@ -28,52 +28,26 @@ export class AutofillBadgeUpdaterService {\n \n-    // Recalculate badges for all active tabs when ciphers or active account changes\n-    combineLatest({\n-      account: this.accountService.activeAccount$,\n-      enableBadgeCounter:\n-        this.badgeSettingsService.enableBadgeCounter$.pipe(distinctUntilChanged()),\n-      ciphers: ciphers$,\n-    })\n-      .pipe(\n+    this.badgeService.setState(StateName, (tab) => {\n+      return combineLatest({\n+        account: this.accountService.activeAccount$,\n+        enableBadgeCounter:\n+          this.badgeSettingsService.enableBadgeCounter$.pipe(distinctUntilChanged()),\n+        ciphers: ciphers$.pipe(delay(100)), // Delay to allow cipherService.getAllDecryptedForUrl to pick up changes\n+      }).pipe(\n         mergeMap(async ({ account, enableBadgeCounter }) => {\n-          if (!account) {\n-            return;\n-          }\n-\n-          const tabs = await this.badgeService.getActiveTabs();\n-\n-          for (const tab of tabs) {\n-            if (!tab.tabId) {\n-              continue;\n-            }\n-            if (enableBadgeCounter) {\n-              await this.setTabState(tab, account.id);\n-            } else {\n-              await this.clearTabState(tab.tabId);\n-            }\n-          }\n-        }),\n-      )\n-      .subscribe();\n-\n-    // Recalculate badge for a specific tab when it becomes active\n-    this.badgeService.activeTabsUpdated$\n-      .pipe(\n-        withLatestFrom(\n-          this.accountService.activeAccount$,\n-          this.badgeSettingsService.enableBadgeCounter$,\n-        ),\n-        mergeMap(async ([tabs, account, enableBadgeCounter]) => {\n           if (!account || !enableBadgeCounter) {\n-            return;\n+            return undefined;\n           }\n \n-          for (const tab of tabs) {\n-            await this.setTabState(tab, account.id);\n-          }\n+          return {\n+            state: {\n+              text: await this.calculateCountText(tab, account.id),\n+            },\n+            priority: BadgeStatePriority.Default,\n+          };\n         }),\n-      )\n-      .subscribe();\n+      );\n+    });\n   }\n \n-  private async setTabState(tab: Tab, userId: UserId) {\n+  private async calculateCountText(tab: Tab, userId: UserId) {\n     if (!tab.tabId) {\n@@ -87,19 +61,6 @@ export class AutofillBadgeUpdaterService {\n     if (cipherCount === 0) {\n-      await this.clearTabState(tab.tabId);\n-      return;\n+      return undefined;\n     }\n \n-    const countText = cipherCount > 9 ? \"9+\" : cipherCount.toString();\n-    await this.badgeService.setState(\n-      StateName(tab.tabId),\n-      BadgeStatePriority.Default,\n-      {\n-        text: countText,\n-      },\n-      tab.tabId,\n-    );\n-  }\n-\n-  private async clearTabState(tabId: number) {\n-    await this.badgeService.clearState(StateName(tabId));\n+    return cipherCount > 9 ? \"9+\" : cipherCount.toString();\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Cesar Gonzalez","training-data":{"loc-added":"1","loc-deleted":"37","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"cesar.a.gonzalezcs@gmail.com","commit-full-message":"","commit-date":"2024-02-23T22:01:41Z","current-rev":"c6b0f70a26","filename":"clients/apps/browser/src/autofill/background/overlay.background.ts","previous-rev":"ae1b6e84be","commit-title":"[PM-6078] Remove Username Masking From Inline Autofill Menu (#7951)","language":"TypeScript","id":"2d51c56319c4e3b495d93d027e2638d051f9a749","model-score":0.42,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts\nindex e2d4b72c33..c2eb12041a 100644\n--- a/apps/browser/src/autofill/background/overlay.background.ts\n+++ b/apps/browser/src/autofill/background/overlay.background.ts\n@@ -181,6 +181,3 @@ class OverlayBackground implements OverlayBackgroundInterface {\n             : buildCipherIcon(this.iconsServerUrl, cipher, isFaviconDisabled),\n-        login:\n-          cipher.type === CipherType.Login\n-            ? { username: this.obscureName(cipher.login.username) }\n-            : null,\n+        login: cipher.type === CipherType.Login ? { username: cipher.login.username } : null,\n         card: cipher.type === CipherType.Card ? cipher.card.subTitle : null,\n@@ -428,35 +425,2 @@ class OverlayBackground implements OverlayBackgroundInterface {\n \n-  /**\n-   * Obscures the username by replacing all but the first and last characters with asterisks.\n-   * If the username is less than 4 characters, only the first character will be shown.\n-   * If the username is 6 or more characters, the first and last characters will be shown.\n-   * The domain will not be obscured.\n-   *\n-   * @param name - The username to obscure\n-   */\n-  private obscureName(name: string): string {\n-    if (!name) {\n-      return \"\";\n-    }\n-\n-    const [username, domain] = name.split(\"@\");\n-    const usernameLength = username?.length;\n-    if (!usernameLength) {\n-      return name;\n-    }\n-\n-    const startingCharacters = username.slice(0, usernameLength > 4 ? 2 : 1);\n-    let numberStars = usernameLength;\n-    if (usernameLength > 4) {\n-      numberStars = usernameLength < 6 ? numberStars - 1 : numberStars - 2;\n-    }\n-\n-    let obscureName = `${startingCharacters}${new Array(numberStars).join(\"*\")}`;\n-    if (usernameLength >= 6) {\n-      obscureName = `${obscureName}${username.slice(-1)}`;\n-    }\n-\n-    return domain ? `${obscureName}@${domain}` : obscureName;\n-  }\n-\n   /**\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Bernd Schoolmann","training-data":{"loc-added":"1","loc-deleted":"98","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mail@quexten.com","commit-full-message":"","commit-date":"2024-10-23T17:05:24Z","current-rev":"74dabb97bf","filename":"clients/libs/common/src/platform/services/system.service.ts","previous-rev":"eff9a423da","commit-title":"Move process reload ownership to key-management (#10853)","language":"TypeScript","id":"950e1a3dda7f03b76878b5a6f28027889fb159b1","model-score":0.4,"author-id":null,"project-id":26215,"delta-file-score":1.044829,"diff":"diff --git a/libs/common/src/platform/services/system.service.ts b/libs/common/src/platform/services/system.service.ts\nindex 357737391c..03e96af75b 100644\n--- a/libs/common/src/platform/services/system.service.ts\n+++ b/libs/common/src/platform/services/system.service.ts\n@@ -1,14 +1,4 @@\n-import { firstValueFrom, map, Subscription, timeout } from \"rxjs\";\n+import { firstValueFrom, Subscription } from \"rxjs\";\n \n-import { BiometricStateService } from \"@bitwarden/key-management\";\n-\n-import { PinServiceAbstraction } from \"../../../../auth/src/common/abstractions\";\n-import { VaultTimeoutSettingsService } from \"../../abstractions/vault-timeout/vault-timeout-settings.service\";\n-import { AccountService } from \"../../auth/abstractions/account.service\";\n-import { AuthService } from \"../../auth/abstractions/auth.service\";\n-import { AuthenticationStatus } from \"../../auth/enums/authentication-status\";\n import { AutofillSettingsServiceAbstraction } from \"../../autofill/services/autofill-settings.service\";\n-import { VaultTimeoutAction } from \"../../enums/vault-timeout-action.enum\";\n-import { UserId } from \"../../types/guid\";\n-import { MessagingService } from \"../abstractions/messaging.service\";\n import { PlatformUtilsService } from \"../abstractions/platform-utils.service\";\n@@ -20,3 +10,2 @@ import { TaskSchedulerService } from \"../scheduling/task-scheduler.service\";\n export class SystemService implements SystemServiceAbstraction {\n-  private reloadInterval: any = null;\n   private clearClipboardTimeoutSubscription: Subscription;\n@@ -25,10 +14,4 @@ export class SystemService implements SystemServiceAbstraction {\n   constructor(\n-    private pinService: PinServiceAbstraction,\n-    private messagingService: MessagingService,\n     private platformUtilsService: PlatformUtilsService,\n-    private reloadCallback: () => Promise<void> = null,\n     private autofillSettingsService: AutofillSettingsServiceAbstraction,\n-    private vaultTimeoutSettingsService: VaultTimeoutSettingsService,\n-    private biometricStateService: BiometricStateService,\n-    private accountService: AccountService,\n     private taskSchedulerService: TaskSchedulerService,\n@@ -41,82 +24,2 @@ export class SystemService implements SystemServiceAbstraction {\n \n-  async startProcessReload(authService: AuthService): Promise<void> {\n-    const accounts = await firstValueFrom(this.accountService.accounts$);\n-    if (accounts != null) {\n-      const keys = Object.keys(accounts);\n-      if (keys.length > 0) {\n-        for (const userId of keys) {\n-          let status = await firstValueFrom(authService.authStatusFor$(userId as UserId));\n-          status = await authService.getAuthStatus(userId);\n-          if (status === AuthenticationStatus.Unlocked) {\n-            return;\n-          }\n-        }\n-      }\n-    }\n-\n-    // A reloadInterval has already been set and is executing\n-    if (this.reloadInterval != null) {\n-      return;\n-    }\n-\n-    // If there is an active user, check if they have a pinKeyEncryptedUserKeyEphemeral. If so, prevent process reload upon lock.\n-    const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;\n-    if (userId != null) {\n-      const ephemeralPin = await this.pinService.getPinKeyEncryptedUserKeyEphemeral(userId);\n-      if (ephemeralPin != null) {\n-        return;\n-      }\n-    }\n-\n-    this.cancelProcessReload();\n-    await this.executeProcessReload();\n-  }\n-\n-  private async executeProcessReload() {\n-    const biometricLockedFingerprintValidated = await firstValueFrom(\n-      this.biometricStateService.fingerprintValidated$,\n-    );\n-    if (!biometricLockedFingerprintValidated) {\n-      clearInterval(this.reloadInterval);\n-      this.reloadInterval = null;\n-\n-      const activeUserId = await firstValueFrom(\n-        this.accountService.activeAccount$.pipe(\n-          map((a) => a?.id),\n-          timeout(500),\n-        ),\n-      );\n-      // Replace current active user if they will be logged out on reload\n-      if (activeUserId != null) {\n-        const timeoutAction = await firstValueFrom(\n-          this.vaultTimeoutSettingsService\n-            .getVaultTimeoutActionByUserId$(activeUserId)\n-            .pipe(timeout(500)), // safety feature to avoid this call hanging and stopping process reload from clearing memory\n-        );\n-        if (timeoutAction === VaultTimeoutAction.LogOut) {\n-          const nextUser = await firstValueFrom(\n-            this.accountService.nextUpAccount$.pipe(map((account) => account?.id ?? null)),\n-          );\n-          await this.accountService.switchAccount(nextUser);\n-        }\n-      }\n-\n-      this.messagingService.send(\"reloadProcess\");\n-      if (this.reloadCallback != null) {\n-        await this.reloadCallback();\n-      }\n-      return;\n-    }\n-    if (this.reloadInterval == null) {\n-      this.reloadInterval = setInterval(async () => await this.executeProcessReload(), 1000);\n-    }\n-  }\n-\n-  cancelProcessReload(): void {\n-    if (this.reloadInterval != null) {\n-      clearInterval(this.reloadInterval);\n-      this.reloadInterval = null;\n-    }\n-  }\n-\n   async clearClipboard(clipboardValue: string, timeoutMs: number = null): Promise<void> {\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Bernd Schoolmann","training-data":{"loc-added":"87","loc-deleted":"20","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.17","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"mail@quexten.com","commit-full-message":"* Remove AES128CBC-HMAC encryption\n\n* Increase test coverage\n\n* Refactor symmetric keys and increase test coverage\n\n* Re-add type 0 encryption\n\n* Fix ts strict warning\n\n* Re-add support for encrypt hmac-less aes\n\n* Add comment about inner()\n\n* Update comment\n\n* Deduplicate encryption type check\n\n* Undo test changes\n\n* Lift out encryption type check to before splitting by encryption type\n\n* Change null to undefined\n\n* Fix test","commit-date":"2025-04-08T10:42:42Z","current-rev":"cf0e693caa","filename":"clients/libs/common/src/platform/models/domain/symmetric-crypto-key.ts","previous-rev":"81350a2ee1","commit-title":"[PM-18697] Add new symmetric key runtime representation and move encrypt service to it (#13578)","language":"TypeScript","id":"eb215feac3694dda370afdc761d2af76fbae3f73","model-score":0.39,"author-id":null,"project-id":26215,"delta-file-score":0.36371157,"diff":"diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts\nindex 372b869fd9..45e15c1f60 100644\n--- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts\n+++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts\n@@ -7,3 +7,21 @@ import { EncryptionType } from \"../../enums\";\n \n+export type Aes256CbcHmacKey = {\n+  type: EncryptionType.AesCbc256_HmacSha256_B64;\n+  encryptionKey: Uint8Array;\n+  authenticationKey: Uint8Array;\n+};\n+\n+export type Aes256CbcKey = {\n+  type: EncryptionType.AesCbc256_B64;\n+  encryptionKey: Uint8Array;\n+};\n+\n+/**\n+ *  A symmetric crypto key represents a symmetric key usable for symmetric encryption and decryption operations.\n+ *  The specific algorithm used is private to the key, and should only be exposed to encrypt service implementations.\n+ *  This can be done via `inner()`.\n+ */\n export class SymmetricCryptoKey {\n+  private innerKey: Aes256CbcHmacKey | Aes256CbcKey;\n+\n   key: Uint8Array;\n@@ -19,3 +37,6 @@ export class SymmetricCryptoKey {\n \n-  constructor(key: Uint8Array, encType?: EncryptionType) {\n+  /**\n+   * @param key The key in one of the permitted serialization formats\n+   */\n+  constructor(key: Uint8Array) {\n     if (key == null) {\n@@ -24,29 +45,33 @@ export class SymmetricCryptoKey {\n \n-    if (encType == null) {\n-      if (key.byteLength === 32) {\n-        encType = EncryptionType.AesCbc256_B64;\n-      } else if (key.byteLength === 64) {\n-        encType = EncryptionType.AesCbc256_HmacSha256_B64;\n-      } else {\n-        throw new Error(\"Unable to determine encType.\");\n-      }\n-    }\n-\n-    this.key = key;\n-    this.encType = encType;\n+    if (key.byteLength === 32) {\n+      this.innerKey = {\n+        type: EncryptionType.AesCbc256_B64,\n+        encryptionKey: key,\n+      };\n+      this.encType = EncryptionType.AesCbc256_B64;\n+      this.key = key;\n+      this.keyB64 = Utils.fromBufferToB64(this.key);\n \n-    if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) {\n       this.encKey = key;\n+      this.encKeyB64 = Utils.fromBufferToB64(this.encKey);\n+\n       this.macKey = null;\n-    } else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && key.byteLength === 64) {\n+      this.macKeyB64 = undefined;\n+    } else if (key.byteLength === 64) {\n+      this.innerKey = {\n+        type: EncryptionType.AesCbc256_HmacSha256_B64,\n+        encryptionKey: key.slice(0, 32),\n+        authenticationKey: key.slice(32),\n+      };\n+      this.encType = EncryptionType.AesCbc256_HmacSha256_B64;\n+      this.key = key;\n+      this.keyB64 = Utils.fromBufferToB64(this.key);\n+\n       this.encKey = key.slice(0, 32);\n-      this.macKey = key.slice(32, 64);\n-    } else {\n-      throw new Error(\"Unsupported encType/key length.\");\n-    }\n+      this.encKeyB64 = Utils.fromBufferToB64(this.encKey);\n \n-    this.keyB64 = Utils.fromBufferToB64(this.key);\n-    this.encKeyB64 = Utils.fromBufferToB64(this.encKey);\n-    if (this.macKey != null) {\n+      this.macKey = key.slice(32);\n       this.macKeyB64 = Utils.fromBufferToB64(this.macKey);\n+    } else {\n+      throw new Error(`Unsupported encType/key length ${key.byteLength}`);\n     }\n@@ -59,2 +84,44 @@ export class SymmetricCryptoKey {\n \n+  /**\n+   * It is preferred not to work with the raw key where possible.\n+   * Only use this method if absolutely necessary.\n+   *\n+   * @returns The inner key instance that can be directly used for encryption primitives\n+   */\n+  inner(): Aes256CbcHmacKey | Aes256CbcKey {\n+    return this.innerKey;\n+  }\n+\n+  /**\n+   * @returns The serialized key in base64 format\n+   */\n+  toBase64(): string {\n+    return Utils.fromBufferToB64(this.toEncoded());\n+  }\n+\n+  /**\n+   * Serializes the key to a format that can be written to state or shared\n+   * The currently permitted format is:\n+   * - AesCbc256_B64: 32 bytes (the raw key)\n+   * - AesCbc256_HmacSha256_B64: 64 bytes (32 bytes encryption key, 32 bytes authentication key, concatenated)\n+   *\n+   * @returns The serialized key that can be written to state or encrypted and then written to state / shared\n+   */\n+  toEncoded(): Uint8Array {\n+    if (this.innerKey.type === EncryptionType.AesCbc256_B64) {\n+      return this.innerKey.encryptionKey;\n+    } else if (this.innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {\n+      const encodedKey = new Uint8Array(64);\n+      encodedKey.set(this.innerKey.encryptionKey, 0);\n+      encodedKey.set(this.innerKey.authenticationKey, 32);\n+      return encodedKey;\n+    } else {\n+      throw new Error(\"Unsupported encryption type.\");\n+    }\n+  }\n+\n+  /**\n+   * @param s The serialized key in base64 format\n+   * @returns A SymmetricCryptoKey instance\n+   */\n   static fromString(s: string): SymmetricCryptoKey {\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Stephon Brown","training-data":{"loc-added":"56","loc-deleted":"15","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"sbrown@livefront.com","commit-full-message":"* feat(billing): Refactor DisplayPaymentMethodInlineComponent for external form control\n\n* feat(billing): Integrate external payment method management in PremiumOrgUpgradePayment\nCleanup: Remove debug console.warn in invoice preview refresh\n\n* test(billing): Update PremiumOrgUpgradePaymentComponent tests\n\n* refactor:add non-null assertion for payment method validation\n\n* refactor: use string selectors for ViewChild\n\n* refactor: remove unused `tap` operator\n\n* test: improve component mocking setup\n\n* feat: add payment method validation on upgrade\n\n* refactor(billing): remove unused updatePaymentInParent input","commit-date":"2026-02-24T20:07:43Z","current-rev":"f667507512","filename":"clients/apps/web/src/app/billing/payment/components/display-payment-method-inline.component.ts","previous-rev":"181e4767d8","commit-title":"[PM-32028] Remove Save and Cancel Buttons (#18954)","language":"TypeScript","id":"341cd6a6c622d4b2794ce354c3455b74fa5f9dd8","model-score":0.39,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/apps/web/src/app/billing/payment/components/display-payment-method-inline.component.ts b/apps/web/src/app/billing/payment/components/display-payment-method-inline.component.ts\nindex aa6d15401f..06a6508c6a 100644\n--- a/apps/web/src/app/billing/payment/components/display-payment-method-inline.component.ts\n+++ b/apps/web/src/app/billing/payment/components/display-payment-method-inline.component.ts\n@@ -10,2 +10,3 @@ import {\n } from \"@angular/core\";\n+import { FormGroup } from \"@angular/forms\";\n \n@@ -92,3 +93,3 @@ import { EnterPaymentMethodComponent } from \"./enter-payment-method.component\";\n           #enterPaymentMethodComponent\n-          [includeBillingAddress]=\"true\"\n+          [includeBillingAddress]=\"false\"\n           [group]=\"formGroup\"\n@@ -98,16 +99,18 @@ import { EnterPaymentMethodComponent } from \"./enter-payment-method.component\";\n         </app-enter-payment-method>\n-        <div class=\"tw-mt-4 tw-flex tw-gap-2\">\n-          <button\n-            bitLink\n-            linkType=\"default\"\n-            type=\"button\"\n-            (click)=\"submit()\"\n-            [disabled]=\"formGroup.invalid\"\n-          >\n-            {{ \"save\" | i18n }}\n-          </button>\n-          <button bitLink linkType=\"subtle\" type=\"button\" (click)=\"cancel()\">\n-            {{ \"cancel\" | i18n }}\n-          </button>\n-        </div>\n+        @if (showFormButtons()) {\n+          <div class=\"tw-mt-4 tw-flex tw-gap-2\">\n+            <button\n+              bitLink\n+              linkType=\"default\"\n+              type=\"button\"\n+              (click)=\"submit()\"\n+              [disabled]=\"formGroup.invalid\"\n+            >\n+              {{ \"save\" | i18n }}\n+            </button>\n+            <button bitLink linkType=\"subtle\" type=\"button\" (click)=\"cancel()\">\n+              {{ \"cancel\" | i18n }}\n+            </button>\n+          </div>\n+        }\n       }\n@@ -122,6 +125,7 @@ export class DisplayPaymentMethodInlineComponent {\n   readonly paymentMethod = input.required<MaskedPaymentMethod | null>();\n+  readonly externalFormGroup = input<FormGroup | null>(null);\n+\n   readonly updated = output<MaskedPaymentMethod>();\n-  readonly changingStateChanged = output<boolean>();\n \n-  protected formGroup = EnterPaymentMethodComponent.getFormGroup();\n+  protected formGroup: FormGroup;\n \n@@ -131,5 +135,9 @@ export class DisplayPaymentMethodInlineComponent {\n \n-  protected readonly isChangingPayment = signal(false);\n+  readonly isChangingPayment = signal(false);\n+\n   protected readonly cardBrandIcon = computed(() => getCardBrandIcon(this.paymentMethod()));\n \n+  // Show submit buttons only when component is managing its own form (no external form provided)\n+  protected readonly showFormButtons = computed(() => this.externalFormGroup() === null);\n+\n   private readonly billingClient = inject(SubscriberBillingClient);\n@@ -139,2 +147,7 @@ export class DisplayPaymentMethodInlineComponent {\n \n+  constructor() {\n+    // Use external form group if provided, otherwise create our own\n+    this.formGroup = this.externalFormGroup() ?? EnterPaymentMethodComponent.getFormGroup();\n+  }\n+\n   /**\n@@ -144,5 +157,50 @@ export class DisplayPaymentMethodInlineComponent {\n     this.isChangingPayment.set(true);\n-    this.changingStateChanged.emit(true);\n   };\n \n+  /**\n+   * Public method to get tokenized payment method data.\n+   * Use this when parent component handles submission.\n+   * Parent is responsible for handling billing address separately.\n+   * @returns Promise with tokenized payment method\n+   */\n+  async getTokenizedPaymentMethod(): Promise<any> {\n+    if (!this.formGroup.valid) {\n+      this.formGroup.markAllAsTouched();\n+      throw new Error(\"Form is invalid\");\n+    }\n+\n+    const component = this.enterPaymentMethodComponent();\n+    if (!component) {\n+      throw new Error(\"Payment method component not found\");\n+    }\n+\n+    const paymentMethod = await component.tokenize();\n+    if (!paymentMethod) {\n+      throw new Error(\"Failed to tokenize payment method\");\n+    }\n+\n+    return paymentMethod;\n+  }\n+\n+  /**\n+   * Validates the form and returns whether it's ready for submission.\n+   * Used when parent component handles submission to determine button state.\n+   */\n+  isFormValid(): boolean {\n+    const enterPaymentMethodComponent = this.enterPaymentMethodComponent();\n+    if (enterPaymentMethodComponent) {\n+      return this.enterPaymentMethodComponent()!.validate();\n+    }\n+    return false;\n+  }\n+\n+  /**\n+   * Public method to reset the form and exit edit mode.\n+   * Use this after parent successfully handles the update.\n+   */\n+  resetForm(): void {\n+    this.formGroup.reset();\n+    this.isChangingPayment.set(false);\n+  }\n+\n   /**\n@@ -153,16 +211,3 @@ export class DisplayPaymentMethodInlineComponent {\n     try {\n-      if (!this.formGroup.valid) {\n-        this.formGroup.markAllAsTouched();\n-        throw new Error(\"Form is invalid\");\n-      }\n-\n-      const component = this.enterPaymentMethodComponent();\n-      if (!component) {\n-        throw new Error(\"Payment method component not found\");\n-      }\n-\n-      const paymentMethod = await component.tokenize();\n-      if (!paymentMethod) {\n-        throw new Error(\"Failed to tokenize payment method\");\n-      }\n+      const paymentMethod = await this.getTokenizedPaymentMethod();\n \n@@ -203,5 +248,3 @@ export class DisplayPaymentMethodInlineComponent {\n         this.updated.emit(result.value);\n-        this.isChangingPayment.set(false);\n-        this.changingStateChanged.emit(false);\n-        this.formGroup.reset();\n+        this.resetForm();\n         break;\n@@ -225,5 +268,3 @@ export class DisplayPaymentMethodInlineComponent {\n   protected cancel = (): void => {\n-    this.formGroup.reset();\n-    this.changingStateChanged.emit(false);\n-    this.isChangingPayment.set(false);\n+    this.resetForm();\n   };\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Jordan Aasen","training-data":{"loc-added":"38","loc-deleted":"39","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.26","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"166539328+jaasen-livefront@users.noreply.github.com","commit-full-message":"","commit-date":"2026-04-03T18:29:02Z","current-rev":"d22a5a82c9","filename":"clients/libs/common/src/models/export/fido2-credential.export.ts","previous-rev":"1a1c5f4df2","commit-title":"migrate cipher export and sub-models (#19050)","language":"TypeScript","id":"818a6dcd368b606ffb2b877b0a0759610b016e00","model-score":0.37,"author-id":null,"project-id":26215,"delta-file-score":0.39107287,"diff":"diff --git a/libs/common/src/models/export/fido2-credential.export.ts b/libs/common/src/models/export/fido2-credential.export.ts\nindex 46131a6706..af614a79e9 100644\n--- a/libs/common/src/models/export/fido2-credential.export.ts\n+++ b/libs/common/src/models/export/fido2-credential.export.ts\n@@ -1,3 +1 @@\n-// FIXME: Update this file to be type safe and remove this and next line\n-// @ts-strict-ignore\n import { EncString } from \"../../key-management/crypto/models/enc-string\";\n@@ -30,3 +28,3 @@ export class Fido2CredentialExport {\n     req.discoverable = \"false\";\n-    req.creationDate = null;\n+    req.creationDate = new Date();\n     return req;\n@@ -64,16 +62,16 @@ export class Fido2CredentialExport {\n   static toDomain(req: Fido2CredentialExport, domain = new Fido2Credential()) {\n-    domain.credentialId = req.credentialId != null ? new EncString(req.credentialId) : null;\n-    domain.keyType = req.keyType != null ? new EncString(req.keyType) : null;\n-    domain.keyAlgorithm = req.keyAlgorithm != null ? new EncString(req.keyAlgorithm) : null;\n-    domain.keyCurve = req.keyCurve != null ? new EncString(req.keyCurve) : null;\n-    domain.keyValue = req.keyValue != null ? new EncString(req.keyValue) : null;\n-    domain.rpId = req.rpId != null ? new EncString(req.rpId) : null;\n-    domain.userHandle = req.userHandle != null ? new EncString(req.userHandle) : null;\n-    domain.userName = req.userName != null ? new EncString(req.userName) : null;\n-    domain.counter = req.counter != null ? new EncString(req.counter) : null;\n-    domain.rpName = req.rpName != null ? new EncString(req.rpName) : null;\n+    domain.credentialId = new EncString(req.credentialId);\n+    domain.keyType = new EncString(req.keyType);\n+    domain.keyAlgorithm = new EncString(req.keyAlgorithm);\n+    domain.keyCurve = new EncString(req.keyCurve);\n+    domain.keyValue = new EncString(req.keyValue);\n+    domain.rpId = new EncString(req.rpId);\n+    domain.userHandle = req.userHandle != null ? new EncString(req.userHandle) : undefined;\n+    domain.userName = req.userName != null ? new EncString(req.userName) : undefined;\n+    domain.counter = new EncString(req.counter);\n+    domain.rpName = req.rpName != null ? new EncString(req.rpName) : undefined;\n     domain.userDisplayName =\n-      req.userDisplayName != null ? new EncString(req.userDisplayName) : null;\n-    domain.discoverable = req.discoverable != null ? new EncString(req.discoverable) : null;\n-    domain.creationDate = req.creationDate != null ? new Date(req.creationDate) : null;\n+      req.userDisplayName != null ? new EncString(req.userDisplayName) : undefined;\n+    domain.discoverable = new EncString(req.discoverable);\n+    domain.creationDate = new Date(req.creationDate);\n     return domain;\n@@ -81,15 +79,15 @@ export class Fido2CredentialExport {\n \n-  credentialId: string;\n-  keyType: string;\n-  keyAlgorithm: string;\n-  keyCurve: string;\n-  keyValue: string;\n-  rpId: string;\n-  userHandle: string;\n-  userName: string;\n-  counter: string;\n-  rpName: string;\n-  userDisplayName: string;\n-  discoverable: string;\n-  creationDate: Date;\n+  credentialId: string = \"\";\n+  keyType: string = \"\";\n+  keyAlgorithm: string = \"\";\n+  keyCurve: string = \"\";\n+  keyValue: string = \"\";\n+  rpId: string = \"\";\n+  userHandle?: string;\n+  userName?: string;\n+  counter: string = \"\";\n+  rpName?: string;\n+  userDisplayName?: string;\n+  discoverable: string = \"false\";\n+  creationDate: Date = new Date();\n \n@@ -105,16 +103,17 @@ export class Fido2CredentialExport {\n \n-    this.credentialId = safeGetString(o.credentialId);\n-    this.keyType = safeGetString(o.keyType);\n-    this.keyAlgorithm = safeGetString(o.keyAlgorithm);\n-    this.keyCurve = safeGetString(o.keyCurve);\n-    this.keyValue = safeGetString(o.keyValue);\n-    this.rpId = safeGetString(o.rpId);\n+    this.credentialId = safeGetString(o.credentialId) ?? \"\";\n+    this.keyType = safeGetString(o.keyType) ?? \"\";\n+    this.keyAlgorithm = safeGetString(o.keyAlgorithm) ?? \"\";\n+    this.keyCurve = safeGetString(o.keyCurve) ?? \"\";\n+    this.keyValue = safeGetString(o.keyValue) ?? \"\";\n+    this.rpId = safeGetString(o.rpId) ?? \"\";\n     this.userHandle = safeGetString(o.userHandle);\n     this.userName = safeGetString(o.userName);\n-    this.counter = safeGetString(o instanceof Fido2CredentialView ? String(o.counter) : o.counter);\n+    this.counter =\n+      safeGetString(o instanceof Fido2CredentialView ? String(o.counter) : o.counter) ?? \"\";\n     this.rpName = safeGetString(o.rpName);\n     this.userDisplayName = safeGetString(o.userDisplayName);\n-    this.discoverable = safeGetString(\n-      o instanceof Fido2CredentialView ? String(o.discoverable) : o.discoverable,\n-    );\n+    this.discoverable =\n+      safeGetString(o instanceof Fido2CredentialView ? String(o.discoverable) : o.discoverable) ??\n+      \"false\";\n     this.creationDate = o.creationDate;\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Stephon Brown","training-data":{"loc-added":"67","loc-deleted":"26","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.26","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"sbrown@livefront.com","commit-full-message":"* refactor(billing): Modernize organization plans template with block syntax\n\n* Refactor: Update `@for` loop tracking with `$index`\n\n* Feat: Migrate component properties to Angular signals\n\n* Refactor: Adapt component logic for signal consumption and improve structure\n\n* feat(admin-console): Bind OrganizationPlansComponent inputs directly in template\n\n* refactor(admin-console): Remove ViewChild for OrganizationPlansComponent configuration\n\n* refactor(billing): Switch productTier and plan to input signals\n\n* refactor(billing): Update productTier and plan access to use form controls\n\n* refactor(billing): Rename 'isCreatingNewOrganization' getter\n\n* refactor(billing): Access enterPaymentMethodComponent as a signal\n\n* feat(billing): use createOrganization property for organization creation flow\n\n* refactor(billing): migrate organization plans tests to signals and reactive forms\n\n* refactor(billing): introduce setupMockUpgradeOrganization helper in tests\n\n* refactor(billing): update getters to computed signals\n\n* refactor(billing): update template to use new computed signals\n\n* tests(billing): update tests with computed signals\n\n* feat(billing): update display of specific forms when user is upgrading from premium\n\n* tests(billing): add premium upgrade tests\n\n* fix(billing): update type issues for organization plans\n\n* tests(billing): update failing test\n\n* refactor(billing): rename plan/productTier inputs to initial* for clarity\n\n* refactor(billing): update templates to use renamed initial* inputs\n\n* test(billing): update tests to use renamed initial* inputs\n\n* feat(billing): feature flag upgrade from premium changes\n\n* test(billing): update tests for featureflag\n\n* refactor(ui): Improve HTML templating for organization plans\n\n* refactor(component): Safely derive 'hasPremiumPersonally' signal\n\n* refactor(component): Streamline 'createCloudHosted' method and key handling\n\n* feat(billing): Enable organization key submission for premium upgrades\n\n* test(billing): Enhance and adjust organization plan submission tests\n\n* refactor: remove unused imports and types\n\n* refactor(billing): introduce DTO for organization upgrade request\n\n* feat(billing): implement organization key and collection encryption\n\n* refactor(billing): integrate DTO and encryption logic in upgrade service\n\n* test(billing): update specs and components for organization upgrade DTO\n\n* refactor(billing): relocate FamiliesForEnterpriseSetupComponent to billing module\n\n* refactor(admin-console): revert families-for-enterprise component\n\n* refactor: rename wrappedPrivateKey to encryptedPrivateKey\n\n* refactor(billing): rename encrypted private key variable\n\n* feat(billing): add tier ids and types\n\n* refactor(billing): remove unused imports and sync service\n\n* fix(billing): correct tier passing in upgrade function\n\n* feat(billing): add conversion function from product tier to subscription tier id\n\n* refactor(billing): enhance organization encryption data generation\n\n* fix(billing): enable response for account upgrade\n\n* fix(billing): pass plan tier instead of plan details\n\n* refactor(test): remove unnecessary organization service mock\n\n* test: update upgrade service tests\n\n* tests(billing): update tests\n\n* refactor(billing): import account type\nfeat(billing): add organization creation message\n\n* refactor(billing): remove unused imports and constants\n\n* refactor(billing): remove unused viewchild\n\n* fix(billing): remove selectedfile variable\n\n* feat(billing): update organization upgrade logic\n\n* refactor(billing): remove unused upgrade functions\n\n* test: remove unnecessary setupMockEncryptionKeys calls\n\n* refactor: remove unused mockAccountBillingClient\n\n* feat: introduce PremiumOrgUpgradeService\n\n* refactor: simplify form value access and remove accountbilling\n\n* fix(billing): handle type validation\n\n* test(billing): add initial plan and product tier input tests\n\n* feat(billing): set initial plan and product tier values\n\n* refactor(billing): ensure selected plan exists before accessing type","commit-date":"2026-03-02T17:16:29Z","current-rev":"2facb7e04f","filename":"clients/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/services/premium-org-upgrade.service.ts","previous-rev":"52c551c0fe","commit-title":"[PM-29823] New Organization Upgrade Path (#19080)","language":"TypeScript","id":"cc8740476bf06c3cc7b0b5eb630e9006354bfc3f","model-score":0.33,"author-id":null,"project-id":26215,"delta-file-score":0.39107287,"diff":"diff --git a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/services/premium-org-upgrade.service.ts b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/services/premium-org-upgrade.service.ts\nindex 59c97e0373..ed394b5682 100644\n--- a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/services/premium-org-upgrade.service.ts\n+++ b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/services/premium-org-upgrade.service.ts\n@@ -1,5 +1,3 @@\n import { Injectable } from \"@angular/core\";\n-import { firstValueFrom } from \"rxjs\";\n \n-import { OrganizationService } from \"@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction\";\n import { Account } from \"@bitwarden/common/auth/abstractions/account.service\";\n@@ -9,6 +7,12 @@ import {\n   BusinessSubscriptionPricingTierId,\n+  BusinessSubscriptionPricingTierIds,\n   PersonalSubscriptionPricingTier,\n   PersonalSubscriptionPricingTierId,\n+  PersonalSubscriptionPricingTierIds,\n   SubscriptionCadenceIds,\n } from \"@bitwarden/common/billing/types/subscription-pricing-tier\";\n+import { EncryptService } from \"@bitwarden/common/key-management/crypto/abstractions/encrypt.service\";\n+import { EncString } from \"@bitwarden/common/key-management/crypto/models/enc-string\";\n+import { I18nService } from \"@bitwarden/common/platform/abstractions/i18n.service\";\n+import { SymmetricCryptoKey } from \"@bitwarden/common/platform/models/domain/symmetric-crypto-key\";\n import { OrgKey } from \"@bitwarden/common/types/key\";\n@@ -16,2 +20,3 @@ import { SyncService } from \"@bitwarden/common/vault/abstractions/sync/sync.serv\n import { KeyService } from \"@bitwarden/key-management\";\n+import { UserId } from \"@bitwarden/user-core\";\n \n@@ -48,5 +53,6 @@ export class PremiumOrgUpgradeService {\n     private previewInvoiceClient: PreviewInvoiceClient,\n-    private syncService: SyncService,\n     private keyService: KeyService,\n-    private organizationService: OrganizationService,\n+    private i18nService: I18nService,\n+    private encryptService: EncryptService,\n+    private syncService: SyncService,\n   ) {}\n@@ -74,3 +80,3 @@ export class PremiumOrgUpgradeService {\n     organizationName: string,\n-    planDetails: PremiumOrgUpgradePlanDetails,\n+    tier: PersonalSubscriptionPricingTierId | BusinessSubscriptionPricingTierId,\n     billingAddress: BillingAddress,\n@@ -85,16 +91,15 @@ export class PremiumOrgUpgradeService {\n \n-    const tier: ProductTierType = this.ProductTierTypeFromSubscriptionTierId(planDetails.tier);\n-    const [encryptedKey] = await this.keyService.makeOrgKey<OrgKey>(account.id);\n-\n-    if (!encryptedKey.encryptedString) {\n-      throw new Error(\"Failed to generate encrypted organization key\");\n-    }\n+    const productTier: ProductTierType = this.ProductTierTypeFromSubscriptionTierId(tier);\n+    const encryptionData = await this.generateOrganizationEncryptionData(account.id);\n \n-    await this.accountBillingClient.upgradePremiumToOrganization(\n+    const orgId = await this.accountBillingClient.upgradePremiumToOrganization({\n       organizationName,\n-      encryptedKey.encryptedString,\n-      tier,\n-      SubscriptionCadenceIds.Annually,\n+      organizationKey: encryptionData.key,\n+      collectionName: encryptionData.collectionCt,\n+      publicKey: encryptionData.orgKeys[0],\n+      encryptedPrivateKey: encryptionData.orgKeys[1].encryptedString as string,\n+      planTier: productTier,\n+      cadence: SubscriptionCadenceIds.Annually,\n       billingAddress,\n-    );\n+    });\n \n@@ -102,12 +107,3 @@ export class PremiumOrgUpgradeService {\n \n-    // Get the newly created organization\n-    const organizations = await firstValueFrom(this.organizationService.organizations$(account.id));\n-\n-    const newOrg = organizations?.find((org) => org.name === organizationName && org.isOwner);\n-\n-    if (!newOrg) {\n-      throw new Error(\"Failed to find newly created organization\");\n-    }\n-\n-    return newOrg.id;\n+    return orgId;\n   }\n@@ -128,2 +124,47 @@ export class PremiumOrgUpgradeService {\n   }\n+\n+  SubscriptionTierIdFromProductTier(\n+    productTier: ProductTierType,\n+  ): BusinessSubscriptionPricingTierId | PersonalSubscriptionPricingTierId {\n+    switch (productTier) {\n+      case ProductTierType.Families:\n+        return PersonalSubscriptionPricingTierIds.Families;\n+      case ProductTierType.Teams:\n+        return BusinessSubscriptionPricingTierIds.Teams;\n+      case ProductTierType.Enterprise:\n+        return BusinessSubscriptionPricingTierIds.Enterprise;\n+      default:\n+        throw new Error(`Unsupported product tier: ${productTier}`);\n+    }\n+  }\n+\n+  /**\n+   * Generates encryption data needed for creating a new organization.\n+   * Uses the active user account signal to get the user ID.\n+   * @returns Organization encryption data including keys and encrypted collection name\n+   */\n+  async generateOrganizationEncryptionData(activeUserId: UserId): Promise<{\n+    key: string;\n+    collectionCt: string;\n+    orgKeys: [string, EncString];\n+    orgKey: SymmetricCryptoKey;\n+    activeUserId: UserId;\n+  }> {\n+    const orgKey = await this.keyService.makeOrgKey<OrgKey>(activeUserId);\n+    const key = orgKey[0].encryptedString as string;\n+    const collection = await this.encryptService.encryptString(\n+      this.i18nService.t(\"defaultCollection\"),\n+      orgKey[1],\n+    );\n+    const collectionCt = collection.encryptedString as string;\n+    const orgKeys = await this.keyService.makeKeyPair(orgKey[1]);\n+\n+    return {\n+      key,\n+      collectionCt,\n+      orgKeys,\n+      orgKey: orgKey[1],\n+      activeUserId,\n+    };\n+  }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Bernd Schoolmann","training-data":{"loc-added":"7","loc-deleted":"61","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"2.34","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"mail@quexten.com","commit-full-message":"* Implement register SDK service\n\n* Relative import\n\n* Relative import\n\n* Rename to registerClient\n\n* Update libs/common/src/platform/abstractions/sdk/register-sdk.service.ts\n\nCo-authored-by: Derek Nance <dnance@bitwarden.com>\n\n* Rename\n\n---------\n\nCo-authored-by: Derek Nance <dnance@bitwarden.com>","commit-date":"2025-11-26T14:47:20Z","current-rev":"5c7e78a80f","filename":"clients/libs/common/src/platform/services/sdk/default-sdk.service.ts","previous-rev":"72024e71d9","commit-title":"[PM-27835] Implement register SDK service (#17632)","language":"TypeScript","id":"f91e0774afaaca8b0badf761aee90a1497145418","model-score":0.3,"author-id":null,"project-id":26215,"delta-file-score":0.6880334,"diff":"diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts\nindex eb663c6f92..6e7bcbb197 100644\n--- a/libs/common/src/platform/services/sdk/default-sdk.service.ts\n+++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts\n@@ -24,3 +24,2 @@ import {\n   ClientSettings,\n-  DeviceType as SdkDeviceType,\n   TokenProvider,\n@@ -31,3 +30,2 @@ import { ApiService } from \"../../../abstractions/api.service\";\n import { AccountInfo, AccountService } from \"../../../auth/abstractions/account.service\";\n-import { DeviceType } from \"../../../enums/device-type.enum\";\n import { EncryptedString, EncString } from \"../../../key-management/crypto/models/enc-string\";\n@@ -41,3 +39,8 @@ import { SdkClientFactory } from \"../../abstractions/sdk/sdk-client-factory\";\n import { SdkLoadService } from \"../../abstractions/sdk/sdk-load.service\";\n-import { asUuid, SdkService, UserNotLoggedInError } from \"../../abstractions/sdk/sdk.service\";\n+import {\n+  asUuid,\n+  SdkService,\n+  toSdkDevice,\n+  UserNotLoggedInError,\n+} from \"../../abstractions/sdk/sdk.service\";\n import { compareValues } from \"../../misc/compare-values\";\n@@ -299,3 +302,3 @@ export class DefaultSdkService implements SdkService {\n       identityUrl: env.getIdentityUrl(),\n-      deviceType: this.toDevice(this.platformUtilsService.getDevice()),\n+      deviceType: toSdkDevice(this.platformUtilsService.getDevice()),\n       userAgent: this.userAgent ?? navigator.userAgent,\n@@ -303,59 +306,2 @@ export class DefaultSdkService implements SdkService {\n   }\n-\n-  private toDevice(device: DeviceType): SdkDeviceType {\n-    switch (device) {\n-      case DeviceType.Android:\n-        return \"Android\";\n-      case DeviceType.iOS:\n-        return \"iOS\";\n-      case DeviceType.ChromeExtension:\n-        return \"ChromeExtension\";\n-      case DeviceType.FirefoxExtension:\n-        return \"FirefoxExtension\";\n-      case DeviceType.OperaExtension:\n-        return \"OperaExtension\";\n-      case DeviceType.EdgeExtension:\n-        return \"EdgeExtension\";\n-      case DeviceType.WindowsDesktop:\n-        return \"WindowsDesktop\";\n-      case DeviceType.MacOsDesktop:\n-        return \"MacOsDesktop\";\n-      case DeviceType.LinuxDesktop:\n-        return \"LinuxDesktop\";\n-      case DeviceType.ChromeBrowser:\n-        return \"ChromeBrowser\";\n-      case DeviceType.FirefoxBrowser:\n-        return \"FirefoxBrowser\";\n-      case DeviceType.OperaBrowser:\n-        return \"OperaBrowser\";\n-      case DeviceType.EdgeBrowser:\n-        return \"EdgeBrowser\";\n-      case DeviceType.IEBrowser:\n-        return \"IEBrowser\";\n-      case DeviceType.UnknownBrowser:\n-        return \"UnknownBrowser\";\n-      case DeviceType.AndroidAmazon:\n-        return \"AndroidAmazon\";\n-      case DeviceType.UWP:\n-        return \"UWP\";\n-      case DeviceType.SafariBrowser:\n-        return \"SafariBrowser\";\n-      case DeviceType.VivaldiBrowser:\n-        return \"VivaldiBrowser\";\n-      case DeviceType.VivaldiExtension:\n-        return \"VivaldiExtension\";\n-      case DeviceType.SafariExtension:\n-        return \"SafariExtension\";\n-      case DeviceType.Server:\n-        return \"Server\";\n-      case DeviceType.WindowsCLI:\n-        return \"WindowsCLI\";\n-      case DeviceType.MacOsCLI:\n-        return \"MacOsCLI\";\n-      case DeviceType.LinuxCLI:\n-        return \"LinuxCLI\";\n-      default:\n-        return \"SDK\";\n-    }\n-  }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Brandon Treston","training-data":{"loc-added":"4","loc-deleted":"43","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"btreston@bitwarden.com","commit-full-message":"","commit-date":"2026-03-25T18:09:55Z","current-rev":"473929f0d8","filename":"clients/apps/web/src/app/admin-console/organizations/members/members.component.ts","previous-rev":"c7544e40c2","commit-title":"remove feature flagged logic (#19718)","language":"TypeScript","id":"559703b9fa2091d81e9c61f8f3e18aae400c8a79","model-score":0.29,"author-id":null,"project-id":26215,"delta-file-score":0.3009901,"diff":"diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts\nindex 6b93edc8c6..1655765a2c 100644\n--- a/apps/web/src/app/admin-console/organizations/members/members.component.ts\n+++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts\n@@ -35,4 +35,2 @@ import { OrganizationMetadataServiceAbstraction } from \"@bitwarden/common/billin\n import { OrganizationBillingMetadataResponse } from \"@bitwarden/common/billing/models/response/organization-billing-metadata.response\";\n-import { FeatureFlag } from \"@bitwarden/common/enums/feature-flag.enum\";\n-import { ConfigService } from \"@bitwarden/common/platform/abstractions/config/config.service\";\n import { EnvironmentService } from \"@bitwarden/common/platform/abstractions/environment.service\";\n@@ -48,3 +46,2 @@ import { OrganizationWarningsService } from \"@bitwarden/web-vault/app/billing/or\n import {\n-  CloudBulkReinviteLimit,\n   MaxCheckedCount,\n@@ -105,3 +102,2 @@ export class MembersComponent {\n   private memberExportService = inject(MemberExportService);\n-  private configService = inject(ConfigService);\n \n@@ -150,6 +146,2 @@ export class MembersComponent {\n \n-  protected readonly bulkReinviteUIEnabled = toSignal(\n-    this.configService.getFeatureFlag$(FeatureFlag.BulkReinviteUI),\n-  );\n-\n   protected billingMetadata$: Observable<OrganizationBillingMetadataResponse>;\n@@ -402,18 +394,5 @@ export class MembersComponent {\n     const allInvitedUsers = users.filter((u) => u.status === OrganizationUserStatusType.Invited);\n+    const invitedCount = allInvitedUsers.length;\n \n-    // Capture the original count BEFORE enforcing the limit\n-    const originalInvitedCount = allInvitedUsers.length;\n-\n-    // In cloud environments, limit invited users and uncheck the excess\n-    let filteredUsers: OrganizationUserView[];\n-    if (this.dataSource().isIncreasedBulkLimitEnabled() && !this.bulkReinviteUIEnabled()) {\n-      filteredUsers = this.dataSource().limitAndUncheckExcess(\n-        allInvitedUsers,\n-        CloudBulkReinviteLimit,\n-      );\n-    } else {\n-      filteredUsers = allInvitedUsers;\n-    }\n-\n-    if (filteredUsers.length <= 0) {\n+    if (invitedCount <= 0) {\n       this.toastService.showToast({\n@@ -426,3 +405,3 @@ export class MembersComponent {\n \n-    const result = await this.memberActionsService.bulkReinvite(organization, filteredUsers);\n+    const result = await this.memberActionsService.bulkReinvite(organization, allInvitedUsers);\n \n@@ -432,28 +411,10 @@ export class MembersComponent {\n \n-    // In cloud environments, show toast instead of dialog\n     if (this.dataSource().isIncreasedBulkLimitEnabled()) {\n-      const selectedCount = originalInvitedCount;\n-      const invitedCount = filteredUsers.length;\n-\n-      // Only show limited toast if feature flag is disabled and limit was applied\n-      if (!this.bulkReinviteUIEnabled() && selectedCount > CloudBulkReinviteLimit) {\n-        const excludedCount = selectedCount - CloudBulkReinviteLimit;\n-        this.toastService.showToast({\n-          variant: \"success\",\n-          message: this.i18nService.t(\n-            \"bulkReinviteLimitedSuccessToast\",\n-            CloudBulkReinviteLimit.toLocaleString(),\n-            selectedCount.toLocaleString(),\n-            excludedCount.toLocaleString(),\n-          ),\n-        });\n-      } else {\n-        this.toastService.showToast({\n-          variant: \"success\",\n-          message:\n-            invitedCount === 1\n-              ? this.i18nService.t(\"reinviteSuccessToast\")\n-              : this.i18nService.t(\"bulkReinviteSentToast\", invitedCount.toString()),\n-        });\n-      }\n+      this.toastService.showToast({\n+        variant: \"success\",\n+        message:\n+          invitedCount === 1\n+            ? this.i18nService.t(\"reinviteSuccessToast\")\n+            : this.i18nService.t(\"bulkReinviteSentToast\", invitedCount.toString()),\n+      });\n     } else {\n@@ -462,3 +423,3 @@ export class MembersComponent {\n         users,\n-        filteredUsers,\n+        allInvitedUsers,\n         Promise.resolve(result.successful),\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"✨ Audrey ✨","training-data":{"loc-added":"57","loc-deleted":"86","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"2.79","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"ajensen@bitwarden.com","commit-full-message":"* introduce email-randomizer\r\n* introduce email-calculator\r\n* introduce password-randomizer\r\n* introduce username-randomizer\r\n* move randomizer abstraction","commit-date":"2024-07-19T20:34:39Z","current-rev":"e22568f05a","filename":"clients/libs/tools/generator/core/src/strategies/password-generator-strategy.ts","previous-rev":"5b5c165e10","commit-title":"[PM-9422] generator engines (#10032)","language":"TypeScript","id":"c8945e02587c83251feef356e62f7bc348bcde13","model-score":0.28,"author-id":null,"project-id":26215,"delta-file-score":1.398546,"diff":"diff --git a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts\nindex d8e59d3105..587c5609e0 100644\n--- a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts\n+++ b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts\n@@ -3,7 +3,8 @@ import { StateProvider } from \"@bitwarden/common/platform/state\";\n \n-import { GeneratorStrategy, Randomizer } from \"../abstractions\";\n+import { GeneratorStrategy } from \"../abstractions\";\n import { Policies, DefaultPasswordGenerationOptions } from \"../data\";\n+import { PasswordRandomizer } from \"../engine\";\n import { mapPolicyToEvaluator } from \"../rx\";\n import { PasswordGenerationOptions, PasswordGeneratorPolicy } from \"../types\";\n-import { clone$PerUserId, sharedStateByUserId } from \"../util\";\n+import { clone$PerUserId, sharedStateByUserId, sum } from \"../util\";\n \n@@ -19,3 +20,3 @@ export class PasswordGeneratorStrategy\n   constructor(\n-    private randomizer: Randomizer,\n+    private randomizer: PasswordRandomizer,\n     private stateProvider: StateProvider,\n@@ -33,91 +34,61 @@ export class PasswordGeneratorStrategy\n   async generate(options: PasswordGenerationOptions): Promise<string> {\n-    const o = { ...DefaultPasswordGenerationOptions, ...options };\n-    let positions: string[] = [];\n-    if (o.lowercase && o.minLowercase > 0) {\n-      for (let i = 0; i < o.minLowercase; i++) {\n-        positions.push(\"l\");\n-      }\n-    }\n-    if (o.uppercase && o.minUppercase > 0) {\n-      for (let i = 0; i < o.minUppercase; i++) {\n-        positions.push(\"u\");\n-      }\n-    }\n-    if (o.number && o.minNumber > 0) {\n-      for (let i = 0; i < o.minNumber; i++) {\n-        positions.push(\"n\");\n-      }\n-    }\n-    if (o.special && o.minSpecial > 0) {\n-      for (let i = 0; i < o.minSpecial; i++) {\n-        positions.push(\"s\");\n-      }\n-    }\n-    while (positions.length < o.length) {\n-      positions.push(\"a\");\n-    }\n-\n-    // shuffle\n-    positions = await this.randomizer.shuffle(positions);\n-\n-    // build out the char sets\n-    let allCharSet = \"\";\n-\n-    let lowercaseCharSet = \"abcdefghijkmnopqrstuvwxyz\";\n-    if (o.ambiguous) {\n-      lowercaseCharSet += \"l\";\n-    }\n-    if (o.lowercase) {\n-      allCharSet += lowercaseCharSet;\n-    }\n-\n-    let uppercaseCharSet = \"ABCDEFGHJKLMNPQRSTUVWXYZ\";\n-    if (o.ambiguous) {\n-      uppercaseCharSet += \"IO\";\n-    }\n-    if (o.uppercase) {\n-      allCharSet += uppercaseCharSet;\n-    }\n+    // converts password generation option sets, which are defined by\n+    // an \"enabled\" and \"quantity\" parameter, to the password engine's\n+    // parameters, which represent disabled options as `undefined`\n+    // properties.\n+    function process(\n+      // values read from the options\n+      enabled: boolean,\n+      quantity: number,\n+      // value used if an option is missing\n+      defaultEnabled: boolean,\n+      defaultQuantity: number,\n+    ) {\n+      const isEnabled = enabled ?? defaultEnabled;\n+      const actualQuantity = quantity ?? defaultQuantity;\n+      const result = isEnabled ? actualQuantity : undefined;\n \n-    let numberCharSet = \"23456789\";\n-    if (o.ambiguous) {\n-      numberCharSet += \"01\";\n-    }\n-    if (o.number) {\n-      allCharSet += numberCharSet;\n+      return result;\n     }\n \n-    const specialCharSet = \"!@#$%^&*\";\n-    if (o.special) {\n-      allCharSet += specialCharSet;\n-    }\n+    const request = {\n+      uppercase: process(\n+        options.uppercase,\n+        options.minUppercase,\n+        DefaultPasswordGenerationOptions.uppercase,\n+        DefaultPasswordGenerationOptions.minUppercase,\n+      ),\n+      lowercase: process(\n+        options.lowercase,\n+        options.minLowercase,\n+        DefaultPasswordGenerationOptions.lowercase,\n+        DefaultPasswordGenerationOptions.minLowercase,\n+      ),\n+      digits: process(\n+        options.number,\n+        options.minNumber,\n+        DefaultPasswordGenerationOptions.number,\n+        DefaultPasswordGenerationOptions.minNumber,\n+      ),\n+      special: process(\n+        options.special,\n+        options.minSpecial,\n+        DefaultPasswordGenerationOptions.special,\n+        DefaultPasswordGenerationOptions.minSpecial,\n+      ),\n+      ambiguous: options.ambiguous ?? DefaultPasswordGenerationOptions.ambiguous,\n+      all: 0,\n+    };\n \n-    let password = \"\";\n-    for (let i = 0; i < o.length; i++) {\n-      let positionChars: string;\n-      switch (positions[i]) {\n-        case \"l\":\n-          positionChars = lowercaseCharSet;\n-          break;\n-        case \"u\":\n-          positionChars = uppercaseCharSet;\n-          break;\n-        case \"n\":\n-          positionChars = numberCharSet;\n-          break;\n-        case \"s\":\n-          positionChars = specialCharSet;\n-          break;\n-        case \"a\":\n-          positionChars = allCharSet;\n-          break;\n-        default:\n-          break;\n-      }\n+    // engine represents character sets as \"include only\"; you assert how many all\n+    // characters there can be rather than a total length. This conversion has\n+    // the character classes win, so that the result is always consistent with policy\n+    // minimums.\n+    const required = sum(request.uppercase, request.lowercase, request.digits, request.special);\n+    const remaining = (options.length ?? 0) - required;\n+    request.all = Math.max(remaining, 0);\n \n-      const randomCharIndex = await this.randomizer.uniform(0, positionChars.length - 1);\n-      password += positionChars.charAt(randomCharIndex);\n-    }\n+    const result = await this.randomizer.randomAscii(request);\n \n-    return password;\n+    return result;\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Vijay Oommen","training-data":{"loc-added":"57","loc-deleted":"49","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"voommen@livefront.com","commit-full-message":"","commit-date":"2025-06-16T21:33:07Z","current-rev":"a9548f519e","filename":"clients/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/services/member-access-report.service.ts","previous-rev":"fcd24a4d60","commit-title":"[PM-20112] Update Member Access report to use new server model (#15155)","language":"TypeScript","id":"e464bfd0dd9115614cc042fe3c4aa56c138e5979","model-score":0.28,"author-id":null,"project-id":26215,"delta-file-score":0.31179166,"diff":"diff --git a/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/services/member-access-report.service.ts b/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/services/member-access-report.service.ts\nindex 029dce8a40..0039788709 100644\n--- a/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/services/member-access-report.service.ts\n+++ b/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/services/member-access-report.service.ts\n@@ -7,3 +7,3 @@ import { I18nService } from \"@bitwarden/common/platform/abstractions/i18n.servic\n import { EncString } from \"@bitwarden/common/platform/models/domain/enc-string\";\n-import { OrganizationId } from \"@bitwarden/common/types/guid\";\n+import { Guid, OrganizationId } from \"@bitwarden/common/types/guid\";\n import {\n@@ -13,3 +13,3 @@ import {\n \n-import { MemberAccessDetails } from \"../response/member-access-report.response\";\n+import { MemberAccessResponse } from \"../response/member-access-report.response\";\n import { MemberAccessExportItem } from \"../view/member-access-export.view\";\n@@ -36,11 +36,40 @@ export class MemberAccessReportService {\n     const memberAccessData = await this.reportApiService.getMemberAccessData(organizationId);\n-    const memberAccessReportViewCollection = memberAccessData.map((userData) => ({\n-      name: userData.userName,\n-      email: userData.email,\n-      collectionsCount: userData.collectionsCount,\n-      groupsCount: userData.groupsCount,\n-      itemsCount: userData.totalItemCount,\n-      userGuid: userData.userGuid,\n-      usesKeyConnector: userData.usesKeyConnector,\n-    }));\n+\n+    // group member access data by userGuid\n+    const userMap = new Map<Guid, MemberAccessResponse[]>();\n+    memberAccessData.forEach((userData) => {\n+      const userGuid = userData.userGuid;\n+      if (!userMap.has(userGuid)) {\n+        userMap.set(userGuid, []);\n+      }\n+      userMap.get(userGuid)?.push(userData);\n+    });\n+\n+    // aggregate user data\n+    const memberAccessReportViewCollection: MemberAccessReportView[] = [];\n+    userMap.forEach((userDataArray, userGuid) => {\n+      const collectionCount = this.getDistinctCount<string>(\n+        userDataArray.map((data) => data.collectionId).filter((id) => !!id),\n+      );\n+      const groupCount = this.getDistinctCount<string>(\n+        userDataArray.map((data) => data.groupId).filter((id) => !!id),\n+      );\n+      const itemsCount = this.getDistinctCount<Guid>(\n+        userDataArray\n+          .flatMap((data) => data.cipherIds)\n+          .filter((id) => id !== \"00000000-0000-0000-0000-000000000000\"),\n+      );\n+      const aggregatedData = {\n+        userGuid: userGuid,\n+        name: userDataArray[0].userName,\n+        email: userDataArray[0].email,\n+        collectionsCount: collectionCount,\n+        groupsCount: groupCount,\n+        itemsCount: itemsCount,\n+        usesKeyConnector: userDataArray.some((data) => data.usesKeyConnector),\n+      };\n+\n+      memberAccessReportViewCollection.push(aggregatedData);\n+    });\n+\n     return memberAccessReportViewCollection;\n@@ -52,9 +81,4 @@ export class MemberAccessReportService {\n     const memberAccessReports = await this.reportApiService.getMemberAccessData(organizationId);\n-    const collectionNames = memberAccessReports.flatMap((item) =>\n-      item.accessDetails.map((dtl) => {\n-        if (dtl.collectionName) {\n-          return dtl.collectionName.encryptedString;\n-        }\n-      }),\n-    );\n+    const collectionNames = memberAccessReports.map((item) => item.collectionName.encryptedString);\n+\n     const collectionNameMap = new Map(collectionNames.map((col) => [col, \"\"]));\n@@ -66,47 +90,26 @@ export class MemberAccessReportService {\n \n-    const exportItems = memberAccessReports.flatMap((report) => {\n-      // to include users without access details\n-      // which means a user has no groups, collections or items\n-      if (report.accessDetails.length === 0) {\n-        return [\n-          {\n-            email: report.email,\n-            name: report.userName,\n-            twoStepLogin: report.twoFactorEnabled\n-              ? this.i18nService.t(\"memberAccessReportTwoFactorEnabledTrue\")\n-              : this.i18nService.t(\"memberAccessReportTwoFactorEnabledFalse\"),\n-            accountRecovery: report.accountRecoveryEnabled\n-              ? this.i18nService.t(\"memberAccessReportAuthenticationEnabledTrue\")\n-              : this.i18nService.t(\"memberAccessReportAuthenticationEnabledFalse\"),\n-            group: this.i18nService.t(\"memberAccessReportNoGroup\"),\n-            collection: this.i18nService.t(\"memberAccessReportNoCollection\"),\n-            collectionPermission: this.i18nService.t(\"memberAccessReportNoCollectionPermission\"),\n-            totalItems: \"0\",\n-          },\n-        ];\n-      }\n-      const userDetails = report.accessDetails.map((detail) => {\n-        const collectionName = collectionNameMap.get(detail.collectionName.encryptedString);\n-        return {\n-          email: report.email,\n-          name: report.userName,\n-          twoStepLogin: report.twoFactorEnabled\n-            ? this.i18nService.t(\"memberAccessReportTwoFactorEnabledTrue\")\n-            : this.i18nService.t(\"memberAccessReportTwoFactorEnabledFalse\"),\n-          accountRecovery: report.accountRecoveryEnabled\n-            ? this.i18nService.t(\"memberAccessReportAuthenticationEnabledTrue\")\n-            : this.i18nService.t(\"memberAccessReportAuthenticationEnabledFalse\"),\n-          group: detail.groupName\n-            ? detail.groupName\n-            : this.i18nService.t(\"memberAccessReportNoGroup\"),\n-          collection: collectionName\n-            ? collectionName\n-            : this.i18nService.t(\"memberAccessReportNoCollection\"),\n-          collectionPermission: detail.collectionId\n-            ? this.getPermissionText(detail)\n-            : this.i18nService.t(\"memberAccessReportNoCollectionPermission\"),\n-          totalItems: detail.itemCount.toString(),\n-        };\n-      });\n-      return userDetails;\n+    const exportItems = memberAccessReports.map((report) => {\n+      const collectionName = collectionNameMap.get(report.collectionName.encryptedString);\n+      return {\n+        email: report.email,\n+        name: report.userName,\n+        twoStepLogin: report.twoFactorEnabled\n+          ? this.i18nService.t(\"memberAccessReportTwoFactorEnabledTrue\")\n+          : this.i18nService.t(\"memberAccessReportTwoFactorEnabledFalse\"),\n+        accountRecovery: report.accountRecoveryEnabled\n+          ? this.i18nService.t(\"memberAccessReportAuthenticationEnabledTrue\")\n+          : this.i18nService.t(\"memberAccessReportAuthenticationEnabledFalse\"),\n+        group: report.groupName\n+          ? report.groupName\n+          : this.i18nService.t(\"memberAccessReportNoGroup\"),\n+        collection: collectionName\n+          ? collectionName\n+          : this.i18nService.t(\"memberAccessReportNoCollection\"),\n+        collectionPermission: report.collectionId\n+          ? this.getPermissionText(report)\n+          : this.i18nService.t(\"memberAccessReportNoCollectionPermission\"),\n+        totalItems: report.cipherIds\n+          .filter((_) => _ != \"00000000-0000-0000-0000-000000000000\")\n+          .length.toString(),\n+      };\n     });\n@@ -115,3 +118,3 @@ export class MemberAccessReportService {\n \n-  private getPermissionText(accessDetails: MemberAccessDetails): string {\n+  private getPermissionText(accessDetails: MemberAccessResponse): string {\n     const permissionList = getPermissionList();\n@@ -127,2 +130,7 @@ export class MemberAccessReportService {\n   }\n+\n+  private getDistinctCount<T>(items: T[]): number {\n+    const uniqueItems = new Set(items);\n+    return uniqueItems.size;\n+  }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Jimmy Vo","training-data":{"loc-added":"96","loc-deleted":"47","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.26","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"huynhmaivo82@gmail.com","commit-full-message":"It also includes a refactor to decouple the invite and edit user flows.","commit-date":"2025-01-23T19:24:51Z","current-rev":"620affd3d5","filename":"clients/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts","previous-rev":"aa1c0ca0ee","commit-title":"[PM-13755] Exclude revoked users from the occupied seats count (#12277)","language":"TypeScript","id":"9bc2793c21a8a04e666806c1a99624c875219202","model-score":0.27,"author-id":null,"project-id":26215,"delta-file-score":0.54150903,"diff":"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\nindex fbf29602e0..7a30eba9e1 100644\n--- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts\n+++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts\n@@ -57,2 +57,3 @@ import {\n import { commaSeparatedEmails } from \"./validators/comma-separated-emails.validator\";\n+import { inputEmailLimitValidator } from \"./validators/input-email-limit.validator\";\n import { orgSeatLimitReachedValidator } from \"./validators/org-seat-limit-reached.validator\";\n@@ -65,14 +66,24 @@ export enum MemberDialogTab {\n \n-export interface MemberDialogParams {\n-  name: string;\n+interface CommonMemberDialogParams {\n+  isOnSecretsManagerStandalone: boolean;\n   organizationId: string;\n-  organizationUserId: string;\n+}\n+\n+export interface AddMemberDialogParams extends CommonMemberDialogParams {\n+  kind: \"Add\";\n+  occupiedSeatCount: number;\n   allOrganizationUserEmails: string[];\n+}\n+\n+export interface EditMemberDialogParams extends CommonMemberDialogParams {\n+  kind: \"Edit\";\n+  name: string;\n+  organizationUserId: string;\n   usesKeyConnector: boolean;\n-  isOnSecretsManagerStandalone: boolean;\n-  initialTab?: MemberDialogTab;\n-  numSeatsUsed: number;\n   managedByOrganization?: boolean;\n+  initialTab: MemberDialogTab;\n }\n \n+export type MemberDialogParams = EditMemberDialogParams | AddMemberDialogParams;\n+\n export enum MemberDialogResult {\n@@ -100,2 +111,3 @@ export class MemberDialogComponent implements OnDestroy {\n   remainingSeats$: Observable<number>;\n+  editParams$: Observable<EditMemberDialogParams>;\n \n@@ -145,2 +157,8 @@ export class MemberDialogComponent implements OnDestroy {\n \n+  isEditDialogParams(\n+    params: EditMemberDialogParams | AddMemberDialogParams,\n+  ): params is EditMemberDialogParams {\n+    return params.kind === \"Edit\";\n+  }\n+\n   constructor(\n@@ -170,5 +188,20 @@ export class MemberDialogComponent implements OnDestroy {\n \n-    this.editMode = this.params.organizationUserId != null;\n-    this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;\n-    this.title = this.i18nService.t(this.editMode ? \"editMember\" : \"inviteMember\");\n+    let userDetails$;\n+    if (this.isEditDialogParams(this.params)) {\n+      this.editMode = true;\n+      this.title = this.i18nService.t(\"editMember\");\n+      userDetails$ = this.userService.get(\n+        this.params.organizationId,\n+        this.params.organizationUserId,\n+      );\n+      this.tabIndex = this.params.initialTab;\n+      this.editParams$ = of(this.params);\n+    } else {\n+      this.editMode = false;\n+      this.title = this.i18nService.t(\"inviteMember\");\n+      this.editParams$ = of(null);\n+      userDetails$ = of(null);\n+      this.tabIndex = MemberDialogTab.Role;\n+    }\n+\n     this.isOnSecretsManagerStandalone = this.params.isOnSecretsManagerStandalone;\n@@ -189,6 +222,2 @@ export class MemberDialogComponent implements OnDestroy {\n \n-    const userDetails$ = this.params.organizationUserId\n-      ? this.userService.get(this.params.organizationId, this.params.organizationUserId)\n-      : of(null);\n-\n     this.allowAdminAccessToAllCollectionItems$ = this.organization$.pipe(\n@@ -273,3 +302,9 @@ export class MemberDialogComponent implements OnDestroy {\n     this.remainingSeats$ = this.organization$.pipe(\n-      map((organization) => organization.seats - this.params.numSeatsUsed),\n+      map((organization) => {\n+        if (!this.isEditDialogParams(this.params)) {\n+          return organization.seats - this.params.occupiedSeatCount;\n+        }\n+\n+        return organization.seats;\n+      }),\n     );\n@@ -278,2 +313,6 @@ export class MemberDialogComponent implements OnDestroy {\n   private setFormValidators(organization: Organization) {\n+    if (this.isEditDialogParams(this.params)) {\n+      return;\n+    }\n+\n     const emailsControlValidators = [\n@@ -281,2 +320,5 @@ export class MemberDialogComponent implements OnDestroy {\n       commaSeparatedEmails,\n+      inputEmailLimitValidator(organization, (maxEmailsCount: number) =>\n+        this.i18nService.t(\"tooManyEmails\", maxEmailsCount),\n+      ),\n       orgSeatLimitReachedValidator(\n@@ -285,2 +327,3 @@ export class MemberDialogComponent implements OnDestroy {\n         this.i18nService.t(\"subscriptionUpgrade\", organization.seats),\n+        this.params.occupiedSeatCount,\n       ),\n@@ -435,6 +478,16 @@ export class MemberDialogComponent implements OnDestroy {\n \n+    const userView = await this.getUserView();\n+\n+    if (this.isEditDialogParams(this.params)) {\n+      await this.handleEditUser(userView, this.params);\n+    } else {\n+      await this.handleInviteUsers(userView, organization);\n+    }\n+  };\n+\n+  private async getUserView(): Promise<OrganizationUserAdminView> {\n     const userView = new OrganizationUserAdminView();\n-    userView.id = this.params.organizationUserId;\n     userView.organizationId = this.params.organizationId;\n     userView.type = this.formGroup.value.type;\n+\n     userView.permissions = this.setRequestPermissions(\n@@ -443,2 +496,3 @@ export class MemberDialogComponent implements OnDestroy {\n     );\n+\n     userView.collections = this.formGroup.value.access\n@@ -453,26 +507,11 @@ export class MemberDialogComponent implements OnDestroy {\n \n-    if (this.editMode) {\n-      await this.userService.save(userView);\n-    } else {\n-      userView.id = this.params.organizationUserId;\n-      const maxEmailsCount =\n-        organization.productTierType === ProductTierType.TeamsStarter ? 10 : 20;\n-      const emails = [...new Set(this.formGroup.value.emails.trim().split(/\\s*,\\s*/))];\n-      if (emails.length > maxEmailsCount) {\n-        this.formGroup.controls.emails.setErrors({\n-          tooManyEmails: { message: this.i18nService.t(\"tooManyEmails\", maxEmailsCount) },\n-        });\n-        return;\n-      }\n-      if (\n-        organization.hasReseller &&\n-        this.params.numSeatsUsed + emails.length > organization.seats\n-      ) {\n-        this.formGroup.controls.emails.setErrors({\n-          tooManyEmails: { message: this.i18nService.t(\"seatLimitReachedContactYourProvider\") },\n-        });\n-        return;\n-      }\n-      await this.userService.invite(emails, userView);\n-    }\n+    return userView;\n+  }\n+\n+  private async handleEditUser(\n+    userView: OrganizationUserAdminView,\n+    params: EditMemberDialogParams,\n+  ) {\n+    userView.id = params.organizationUserId;\n+    await this.userService.save(userView);\n \n@@ -481,12 +520,23 @@ export class MemberDialogComponent implements OnDestroy {\n       title: null,\n-      message: this.i18nService.t(\n-        this.editMode ? \"editedUserId\" : \"invitedUsers\",\n-        this.params.name,\n-      ),\n+      message: this.i18nService.t(\"editedUserId\", params.name),\n     });\n+\n     this.close(MemberDialogResult.Saved);\n-  };\n+  }\n+\n+  private async handleInviteUsers(userView: OrganizationUserAdminView, organization: Organization) {\n+    const emails = [...new Set(this.formGroup.value.emails.trim().split(/\\s*,\\s*/))];\n+\n+    await this.userService.invite(emails, userView);\n+\n+    this.toastService.showToast({\n+      variant: \"success\",\n+      title: null,\n+      message: this.i18nService.t(\"invitedUsers\"),\n+    });\n+    this.close(MemberDialogResult.Saved);\n+  }\n \n   remove = async () => {\n-    if (!this.editMode) {\n+    if (!this.isEditDialogParams(this.params)) {\n       return;\n@@ -509,3 +559,3 @@ export class MemberDialogComponent implements OnDestroy {\n     if (this.showNoMasterPasswordWarning) {\n-      confirmed = await this.noMasterPasswordConfirmationDialog();\n+      confirmed = await this.noMasterPasswordConfirmationDialog(this.params.name);\n \n@@ -530,3 +580,3 @@ export class MemberDialogComponent implements OnDestroy {\n   revoke = async () => {\n-    if (!this.editMode) {\n+    if (!this.isEditDialogParams(this.params)) {\n       return;\n@@ -546,3 +596,3 @@ export class MemberDialogComponent implements OnDestroy {\n     if (this.showNoMasterPasswordWarning) {\n-      confirmed = await this.noMasterPasswordConfirmationDialog();\n+      confirmed = await this.noMasterPasswordConfirmationDialog(this.params.name);\n \n@@ -568,3 +618,3 @@ export class MemberDialogComponent implements OnDestroy {\n   restore = async () => {\n-    if (!this.editMode) {\n+    if (!this.isEditDialogParams(this.params)) {\n       return;\n@@ -587,3 +637,3 @@ export class MemberDialogComponent implements OnDestroy {\n   delete = async () => {\n-    if (!this.editMode) {\n+    if (!this.isEditDialogParams(this.params)) {\n       return;\n@@ -635,3 +685,3 @@ export class MemberDialogComponent implements OnDestroy {\n \n-  private noMasterPasswordConfirmationDialog() {\n+  private noMasterPasswordConfirmationDialog(username: string) {\n     return this.dialogService.openSimpleDialog({\n@@ -642,3 +692,3 @@ export class MemberDialogComponent implements OnDestroy {\n         key: \"removeOrgUserNoMasterPasswordDesc\",\n-        placeholders: [this.params.name],\n+        placeholders: [username],\n       },\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Oscar Hinton","training-data":{"loc-added":"57","loc-deleted":"84","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"Hinton@users.noreply.github.com","commit-full-message":"We should be able to simplify the angular component by using svg element directly.","commit-date":"2025-10-03T09:51:25Z","current-rev":"a1e226b598","filename":"clients/libs/components/src/avatar/avatar.component.ts","previous-rev":"cb20889a94","commit-title":"[PM-25603] Use angular template for avatar (#15978)","language":"TypeScript","id":"89bc3476b12fde8b67cf202f6f5bbba0a4a5e98a","model-score":0.26,"author-id":null,"project-id":26215,"delta-file-score":0.61278176,"diff":"diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts\nindex 6f83c9ca10..8ece033c73 100644\n--- a/libs/components/src/avatar/avatar.component.ts\n+++ b/libs/components/src/avatar/avatar.component.ts\n@@ -1,4 +1,3 @@\n import { NgClass } from \"@angular/common\";\n-import { Component, OnChanges, input } from \"@angular/core\";\n-import { DomSanitizer, SafeResourceUrl } from \"@angular/platform-browser\";\n+import { Component, computed, input } from \"@angular/core\";\n \n@@ -24,8 +23,30 @@ const SizeClasses: Record<SizeTypes, string[]> = {\n   selector: \"bit-avatar\",\n-  template: `@if (src) {\n-    <img [src]=\"src\" title=\"{{ title() || text() }}\" [ngClass]=\"classList\" />\n-  }`,\n+  template: `\n+    <span [title]=\"title() || text()\">\n+      <svg\n+        xmlns=\"http://www.w3.org/2000/svg\"\n+        pointer-events=\"none\"\n+        [style.backgroundColor]=\"backgroundColor()\"\n+        [ngClass]=\"classList()\"\n+        attr.viewBox=\"0 0 {{ svgSize }} {{ svgSize }}\"\n+      >\n+        <text\n+          text-anchor=\"middle\"\n+          y=\"50%\"\n+          x=\"50%\"\n+          dy=\"0.35em\"\n+          pointer-events=\"auto\"\n+          [attr.fill]=\"textColor()\"\n+          [style.fontWeight]=\"svgFontWeight\"\n+          [style.fontSize.px]=\"svgFontSize\"\n+          font-family='Roboto,\"Helvetica Neue\",Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\"'\n+        >\n+          {{ displayChars() }}\n+        </text>\n+      </svg>\n+    </span>\n+  `,\n   imports: [NgClass],\n })\n-export class AvatarComponent implements OnChanges {\n+export class AvatarComponent {\n   readonly border = input(false);\n@@ -37,32 +58,36 @@ export class AvatarComponent implements OnChanges {\n \n-  private svgCharCount = 2;\n-  private svgFontSize = 20;\n-  private svgFontWeight = 300;\n-  private svgSize = 48;\n-  src?: SafeResourceUrl;\n+  protected readonly svgCharCount = 2;\n+  protected readonly svgFontSize = 20;\n+  protected readonly svgFontWeight = 300;\n+  protected readonly svgSize = 48;\n \n-  constructor(public sanitizer: DomSanitizer) {}\n-\n-  ngOnChanges() {\n-    this.generate();\n-  }\n-\n-  get classList() {\n-    return [\"tw-rounded-full\", \"tw-inline\"]\n+  protected readonly classList = computed(() => {\n+    return [\"tw-rounded-full\"]\n       .concat(SizeClasses[this.size()] ?? [])\n       .concat(this.border() ? [\"tw-border\", \"tw-border-solid\", \"tw-border-secondary-600\"] : []);\n-  }\n+  });\n \n-  private generate() {\n-    const color = this.color();\n-    const text = this.text();\n+  protected readonly backgroundColor = computed(() => {\n     const id = this.id();\n-    if (!text && !color && !id) {\n-      throw new Error(\"Must supply `text`, `color`, or `id` input.\");\n+    const upperCaseText = this.text()?.toUpperCase() ?? \"\";\n+\n+    if (!Utils.isNullOrWhitespace(this.color())) {\n+      return this.color()!;\n     }\n-    let chars: string | null = null;\n-    const upperCaseText = text?.toUpperCase() ?? \"\";\n \n-    chars = this.getFirstLetters(upperCaseText, this.svgCharCount);\n+    if (!Utils.isNullOrWhitespace(id)) {\n+      return Utils.stringToColor(id!.toString());\n+    }\n+\n+    return Utils.stringToColor(upperCaseText);\n+  });\n+\n+  protected readonly textColor = computed(() => {\n+    return Utils.pickTextColorBasedOnBgColor(this.backgroundColor(), 135, true);\n+  });\n+\n+  protected readonly displayChars = computed(() => {\n+    const upperCaseText = this.text()?.toUpperCase() ?? \"\";\n \n+    let chars = this.getFirstLetters(upperCaseText, this.svgCharCount);\n     if (chars == null) {\n@@ -77,26 +102,6 @@ export class AvatarComponent implements OnChanges {\n \n-    let svg: HTMLElement;\n-    let hexColor = color ?? \"\";\n-    if (!Utils.isNullOrWhitespace(hexColor)) {\n-      svg = this.createSvgElement(this.svgSize, hexColor);\n-    } else if (!Utils.isNullOrWhitespace(id ?? \"\")) {\n-      hexColor = Utils.stringToColor(id!.toString());\n-      svg = this.createSvgElement(this.svgSize, hexColor);\n-    } else {\n-      hexColor = Utils.stringToColor(upperCaseText);\n-      svg = this.createSvgElement(this.svgSize, hexColor);\n-    }\n-\n-    const charObj = this.createTextElement(chars, hexColor);\n-    svg.appendChild(charObj);\n-    const html = window.document.createElement(\"div\").appendChild(svg).outerHTML;\n-    const svgHtml = window.btoa(unescape(encodeURIComponent(html)));\n-\n-    // This is safe because the only user provided value, chars is set using `textContent`\n-    this.src = this.sanitizer.bypassSecurityTrustResourceUrl(\n-      \"data:image/svg+xml;base64,\" + svgHtml,\n-    );\n-  }\n+    return chars;\n+  });\n \n-  private getFirstLetters(data: string, count: number): string | null {\n+  private getFirstLetters(data: string, count: number): string | undefined {\n     const parts = data.split(\" \");\n@@ -109,35 +114,3 @@ export class AvatarComponent implements OnChanges {\n     }\n-    return null;\n-  }\n-\n-  private createSvgElement(size: number, color: string): HTMLElement {\n-    const svgTag = window.document.createElement(\"svg\");\n-    svgTag.setAttribute(\"xmlns\", \"http://www.w3.org/2000/svg\");\n-    svgTag.setAttribute(\"pointer-events\", \"none\");\n-    svgTag.setAttribute(\"width\", size.toString());\n-    svgTag.setAttribute(\"height\", size.toString());\n-    svgTag.style.backgroundColor = color;\n-    svgTag.style.width = size + \"px\";\n-    svgTag.style.height = size + \"px\";\n-    return svgTag;\n-  }\n-\n-  private createTextElement(character: string, color: string): HTMLElement {\n-    const textTag = window.document.createElement(\"text\");\n-    textTag.setAttribute(\"text-anchor\", \"middle\");\n-    textTag.setAttribute(\"y\", \"50%\");\n-    textTag.setAttribute(\"x\", \"50%\");\n-    textTag.setAttribute(\"dy\", \"0.35em\");\n-    textTag.setAttribute(\"pointer-events\", \"auto\");\n-    textTag.setAttribute(\"fill\", Utils.pickTextColorBasedOnBgColor(color, 135, true));\n-    textTag.setAttribute(\n-      \"font-family\",\n-      'Roboto,\"Helvetica Neue\",Helvetica,Arial,' +\n-        'sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\"',\n-    );\n-    // Warning do not use innerHTML here, characters are user provided\n-    textTag.textContent = character;\n-    textTag.style.fontWeight = this.svgFontWeight.toString();\n-    textTag.style.fontSize = this.svgFontSize + \"px\";\n-    return textTag;\n+    return undefined;\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Bernd Schoolmann","training-data":{"loc-added":"20","loc-deleted":"30","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"mail@quexten.com","commit-full-message":"* Remove AES128CBC-HMAC encryption\n\n* Increase test coverage\n\n* Refactor symmetric keys and increase test coverage\n\n* Re-add type 0 encryption\n\n* Fix ts strict warning\n\n* Remove old symmetric key representations in symmetriccryptokey\n\n* Fix desktop build\n\n* Fix test\n\n* Fix build\n\n* Update libs/common/src/key-management/crypto/services/web-crypto-function.service.ts\n\nCo-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>\n\n* Update libs/node/src/services/node-crypto-function.service.ts\n\nCo-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>\n\n* Undo changes\n\n* Remove cast\n\n* Undo changes to tests\n\n* Fix linting\n\n* Undo removing new Uint8Array in aesDecryptFastParameters\n\n* Fix merge conflicts\n\n* Fix test\n\n* Fix another test\n\n* Fix test\n\n---------\n\nCo-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>","commit-date":"2025-04-21T14:57:26Z","current-rev":"43b1f55360","filename":"clients/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts","previous-rev":"a250395e6d","commit-title":"[PM-18697] Remove old symmetric key representations in symmetriccryptokey (#13598)","language":"TypeScript","id":"95ec8c7e09f0d752e86808fcf9854247a648fb6b","model-score":0.26,"author-id":null,"project-id":26215,"delta-file-score":0.3009901,"diff":"diff --git a/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts b/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts\nindex 0c80d508b2..430774ca2e 100644\n--- a/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts\n+++ b/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts\n@@ -3,2 +3,3 @@ import * as forge from \"node-forge\";\n \n+import { EncryptionType } from \"../../../platform/enums\";\n import { Utils } from \"../../../platform/misc/utils\";\n@@ -249,33 +250,22 @@ export class WebCryptoFunctionService implements CryptoFunctionService {\n   ): CbcDecryptParameters<string> {\n-    const p = {} as CbcDecryptParameters<string>;\n-    if (key.meta != null) {\n-      p.encKey = key.meta.encKeyByteString;\n-      p.macKey = key.meta.macKeyByteString;\n-    }\n-\n-    if (p.encKey == null) {\n-      p.encKey = forge.util.decode64(key.encKeyB64);\n-    }\n-    p.data = forge.util.decode64(data);\n-    p.iv = forge.util.decode64(iv);\n-    p.macData = p.iv + p.data;\n-    if (p.macKey == null && key.macKeyB64 != null) {\n-      p.macKey = forge.util.decode64(key.macKeyB64);\n-    }\n-    if (mac != null) {\n-      p.mac = forge.util.decode64(mac);\n-    }\n-\n-    // cache byte string keys for later\n-    if (key.meta == null) {\n-      key.meta = {};\n-    }\n-    if (key.meta.encKeyByteString == null) {\n-      key.meta.encKeyByteString = p.encKey;\n-    }\n-    if (p.macKey != null && key.meta.macKeyByteString == null) {\n-      key.meta.macKeyByteString = p.macKey;\n+    const innerKey = key.inner();\n+    if (innerKey.type === EncryptionType.AesCbc256_B64) {\n+      return {\n+        iv: forge.util.decode64(iv),\n+        data: forge.util.decode64(data),\n+        encKey: forge.util.createBuffer(innerKey.encryptionKey).getBytes(),\n+      } as CbcDecryptParameters<string>;\n+    } else if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {\n+      const macData = forge.util.decode64(iv) + forge.util.decode64(data);\n+      return {\n+        iv: forge.util.decode64(iv),\n+        data: forge.util.decode64(data),\n+        encKey: forge.util.createBuffer(innerKey.encryptionKey).getBytes(),\n+        macKey: forge.util.createBuffer(innerKey.authenticationKey).getBytes(),\n+        mac: forge.util.decode64(mac!),\n+        macData,\n+      } as CbcDecryptParameters<string>;\n+    } else {\n+      throw new Error(\"Unsupported encryption type.\");\n     }\n-\n-    return p;\n   }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Jordan Aasen","training-data":{"loc-added":"1","loc-deleted":"59","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.6882083290695"},"author-email":"166539328+jaasen-livefront@users.noreply.github.com","commit-full-message":"","commit-date":"2025-06-18T22:27:34Z","current-rev":"f9b31d2906","filename":"clients/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts","previous-rev":"b35583a5ac","commit-title":"remove legacy attachment upload (#15237)","language":"TypeScript","id":"663ea4eacb6195e4d6cf8d5d5ece4e6e22fa48d3","model-score":0.24,"author-id":null,"project-id":26215,"delta-file-score":0.59155285,"diff":"diff --git a/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts b/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts\nindex 4dd2f7f733..10fa1d9580 100644\n--- a/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts\n+++ b/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts\n@@ -8,3 +8,2 @@ import {\n } from \"../../../platform/abstractions/file-upload/file-upload.service\";\n-import { Utils } from \"../../../platform/misc/utils\";\n import { EncArrayBuffer } from \"../../../platform/models/domain/enc-array-buffer\";\n@@ -49,14 +48,3 @@ export class CipherFileUploadService implements CipherFileUploadServiceAbstracti\n     } catch (e) {\n-      if (\n-        (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) ||\n-        (e as ErrorResponse).statusCode === 405\n-      ) {\n-        response = await this.legacyServerAttachmentFileUpload(\n-          request.adminRequest,\n-          cipher.id,\n-          encFileName,\n-          encData,\n-          dataEncKey[1],\n-        );\n-      } else if (e instanceof ErrorResponse) {\n+      if (e instanceof ErrorResponse) {\n         throw new Error((e as ErrorResponse).getSingleMessage());\n@@ -115,48 +103,2 @@ export class CipherFileUploadService implements CipherFileUploadServiceAbstracti\n   }\n-\n-  /**\n-   * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.\n-   * This method still exists for backward compatibility with old server versions.\n-   */\n-  async legacyServerAttachmentFileUpload(\n-    admin: boolean,\n-    cipherId: string,\n-    encFileName: EncString,\n-    encData: EncArrayBuffer,\n-    key: EncString,\n-  ) {\n-    const fd = new FormData();\n-    try {\n-      const blob = new Blob([encData.buffer], { type: \"application/octet-stream\" });\n-      fd.append(\"key\", key.encryptedString);\n-      fd.append(\"data\", blob, encFileName.encryptedString);\n-    } catch (e) {\n-      if (Utils.isNode && !Utils.isBrowser) {\n-        fd.append(\"key\", key.encryptedString);\n-        fd.append(\n-          \"data\",\n-          Buffer.from(encData.buffer) as any,\n-          {\n-            filepath: encFileName.encryptedString,\n-            contentType: \"application/octet-stream\",\n-          } as any,\n-        );\n-      } else {\n-        throw e;\n-      }\n-    }\n-\n-    let response: CipherResponse;\n-    try {\n-      if (admin) {\n-        response = await this.apiService.postCipherAttachmentAdminLegacy(cipherId, fd);\n-      } else {\n-        response = await this.apiService.postCipherAttachmentLegacy(cipherId, fd);\n-      }\n-    } catch (e) {\n-      throw new Error((e as ErrorResponse).getSingleMessage());\n-    }\n-\n-    return response;\n-  }\n }\n","improvement-type":"Complex Method"},{"architectural-component-id":null,"author-name":"Jared Snider","training-data":{"loc-added":"65","loc-deleted":"30","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"9.536386775820924"},"author-email":"116684653+JaredSnider-Bitwarden@users.noreply.github.com","commit-full-message":"* PM-8112 - Update classes of existing registration icons\r\n\r\n* PM-8112 - Add new icons\r\n\r\n* PM-8112 - Export icons from libs/auth\r\n\r\n* PM-8112 - RegistrationStart - Add new user icon as page icon\r\n\r\n* PM-8112 - Replace RegistrationCheckEmailIcon with new icon so it displays properly\r\n\r\n* PM-8112 - RegistrationFinish - Add new icon across clients\r\n\r\n* PM-8112 - Registration start comp - update page icon and page title on state change to match figma\r\n\r\n* PM-8112 - RegistrationFinish - adding most of framework for changing page title & subtitle when an org invite is in state.\r\n\r\n* PM-8112 - Add joinOrganizationName to all clients translations\r\n\r\n* PM-8112 - RegistrationFinish - Remove default page title & subtitle and let onInit logic figure out what to set based on flows.\r\n\r\n* PM-8112 - RegistrationStart - Fix setAnonLayoutWrapperData calls\r\n\r\n* PM-8112 - RegistrationFinish - simplify qParams init logic to make handling loading and page title and subtitle setting easier.\r\n\r\n* PM-8112 - Registration Link expired - move icon to page icon out of main content\r\n\r\n* PM-8112 - RegistrationFinish - Refactor init logic further into distinct flows.\r\n\r\n* PM-8112 - Fix icons\r\n\r\n* PM-8112 - Extension AppRoutingModule - move sign up start & finish routes under extension anon layout\r\n\r\n* PM-8112 - Fix storybook\r\n\r\n* PM-8112 - Clean up unused prop\r\n\r\n* PM-8112 - RegistrationLockAltIcon tweaks\r\n\r\n* PM-8112 - Update icons to have proper styling\r\n\r\n* PM-8112 - RegistrationUserAddIcon - remove unnecessary svg class\r\n\r\n* PM-8112 - Fix icons","commit-date":"2024-10-16T22:28:27Z","current-rev":"4b67cd24b4","filename":"clients/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts","previous-rev":"e256bde1de","commit-title":"Auth/PM-8112 - UI refresh - Registration Components (#11353)","language":"TypeScript","id":"61412453a0eb48371e48935d1a4f4c745cc04914","model-score":0.16,"author-id":null,"project-id":26215,"delta-file-score":0.29573047,"diff":"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\nindex ef40d95dce..be9d8abe5b 100644\n--- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts\n+++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts\n@@ -3,3 +3,3 @@ import { Component, OnDestroy, OnInit } from \"@angular/core\";\n import { ActivatedRoute, Params, Router, RouterModule } from \"@angular/router\";\n-import { EMPTY, Subject, from, switchMap, takeUntil, tap } from \"rxjs\";\n+import { Subject, firstValueFrom } from \"rxjs\";\n \n@@ -17,2 +17,3 @@ import { ToastService } from \"@bitwarden/components\";\n import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from \"../../../common\";\n+import { AnonLayoutWrapperDataService } from \"../../anon-layout/anon-layout-wrapper-data.service\";\n import { InputPasswordComponent } from \"../../input-password/input-password.component\";\n@@ -62,2 +63,3 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n     private logService: LogService,\n+    private anonLayoutWrapperDataService: AnonLayoutWrapperDataService,\n   ) {}\n@@ -65,48 +67,64 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n   async ngOnInit() {\n-    this.listenForQueryParamChanges();\n-    this.masterPasswordPolicyOptions =\n-      await this.registrationFinishService.getMasterPasswordPolicyOptsFromOrgInvite();\n+    const qParams = await firstValueFrom(this.activatedRoute.queryParams);\n+    this.handleQueryParams(qParams);\n+\n+    if (\n+      qParams.fromEmail &&\n+      qParams.fromEmail === \"true\" &&\n+      this.email &&\n+      this.emailVerificationToken\n+    ) {\n+      await this.initEmailVerificationFlow();\n+    } else {\n+      // Org Invite flow OR registration with email verification disabled Flow\n+      const orgInviteFlow = await this.initOrgInviteFlowIfPresent();\n+\n+      if (!orgInviteFlow) {\n+        this.initRegistrationWithEmailVerificationDisabledFlow();\n+      }\n+    }\n+\n+    this.loading = false;\n   }\n \n-  private listenForQueryParamChanges() {\n-    this.activatedRoute.queryParams\n-      .pipe(\n-        tap((qParams: Params) => {\n-          if (qParams.email != null && qParams.email.indexOf(\"@\") > -1) {\n-            this.email = qParams.email;\n-          }\n+  private handleQueryParams(qParams: Params) {\n+    if (qParams.email != null && qParams.email.indexOf(\"@\") > -1) {\n+      this.email = qParams.email;\n+    }\n \n-          if (qParams.token != null) {\n-            this.emailVerificationToken = qParams.token;\n-          }\n+    if (qParams.token != null) {\n+      this.emailVerificationToken = qParams.token;\n+    }\n \n-          if (qParams.orgSponsoredFreeFamilyPlanToken != null) {\n-            this.orgSponsoredFreeFamilyPlanToken = qParams.orgSponsoredFreeFamilyPlanToken;\n-          }\n+    if (qParams.orgSponsoredFreeFamilyPlanToken != null) {\n+      this.orgSponsoredFreeFamilyPlanToken = qParams.orgSponsoredFreeFamilyPlanToken;\n+    }\n \n-          if (qParams.acceptEmergencyAccessInviteToken != null && qParams.emergencyAccessId) {\n-            this.acceptEmergencyAccessInviteToken = qParams.acceptEmergencyAccessInviteToken;\n-            this.emergencyAccessId = qParams.emergencyAccessId;\n-          }\n-        }),\n-        switchMap((qParams: Params) => {\n-          if (\n-            qParams.fromEmail &&\n-            qParams.fromEmail === \"true\" &&\n-            this.email &&\n-            this.emailVerificationToken\n-          ) {\n-            return from(\n-              this.registerVerificationEmailClicked(this.email, this.emailVerificationToken),\n-            );\n-          } else {\n-            // org invite flow\n-            this.loading = false;\n-            return EMPTY;\n-          }\n-        }),\n+    if (qParams.acceptEmergencyAccessInviteToken != null && qParams.emergencyAccessId) {\n+      this.acceptEmergencyAccessInviteToken = qParams.acceptEmergencyAccessInviteToken;\n+      this.emergencyAccessId = qParams.emergencyAccessId;\n+    }\n+  }\n+\n+  private async initOrgInviteFlowIfPresent(): Promise<boolean> {\n+    this.masterPasswordPolicyOptions =\n+      await this.registrationFinishService.getMasterPasswordPolicyOptsFromOrgInvite();\n+\n+    const orgName = await this.registrationFinishService.getOrgNameFromOrgInvite();\n+    if (orgName) {\n+      // Org invite exists\n+      // Set the page title and subtitle appropriately\n+      this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({\n+        pageTitle: {\n+          key: \"joinOrganizationName\",\n+          placeholders: [orgName],\n+        },\n+        pageSubtitle: {\n+          key: \"finishJoiningThisOrganizationBySettingAMasterPassword\",\n+        },\n+      });\n+      return true;\n+    }\n \n-        takeUntil(this.destroy$),\n-      )\n-      .subscribe();\n+    return false;\n   }\n@@ -164,5 +182,20 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n \n+  private setDefaultPageTitleAndSubtitle() {\n+    this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({\n+      pageTitle: {\n+        key: \"setAStrongPassword\",\n+      },\n+      pageSubtitle: {\n+        key: \"finishCreatingYourAccountBySettingAPassword\",\n+      },\n+    });\n+  }\n+\n+  private async initEmailVerificationFlow() {\n+    this.setDefaultPageTitleAndSubtitle();\n+    await this.registerVerificationEmailClicked(this.email, this.emailVerificationToken);\n+  }\n+\n   private async registerVerificationEmailClicked(email: string, emailVerificationToken: string) {\n     const request = new RegisterVerificationEmailClickedRequest(email, emailVerificationToken);\n-\n     try {\n@@ -176,3 +209,2 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n         });\n-        this.loading = false;\n       }\n@@ -180,3 +212,2 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n       await this.handleRegisterVerificationEmailClickedError(e);\n-      this.loading = false;\n     }\n@@ -206,2 +237,6 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {\n \n+  private initRegistrationWithEmailVerificationDisabledFlow() {\n+    this.setDefaultPageTitleAndSubtitle();\n+  }\n+\n   ngOnDestroy(): void {\n","improvement-type":"Complex Method"}],"change-level":"warning","is-hotspot?":false,"line":35,"what-changed":"CollectionView.canEdit increases in cyclomatic complexity from 10 to 11, threshold = 9","how-to-fix":"There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using [EXTRACT FUNCTION](https://refactoring.com/catalog/extractFunction.html). Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the [DECOMPOSE CONDITIONAL](https://refactoring.com/catalog/decomposeConditional.html) refactoring.","change-type":"degraded"}]},"positive-impact-count":0,"repo":"clients","code-health":8.691880351560897,"version":"3.0","authors":["Thomas Rittson"],"directives":{"added":[],"removed":[]},"positive-findings":{"number-of-types":0,"number-of-files-touched":0,"findings":[]},"notices":{"number-of-types":1,"number-of-files-touched":1,"findings":[{"why-it-occurs":"String is a generic type that fail to capture the constraints of the domain object it represents. In this module, 77 % of all function arguments are string types.","name":"String Heavy Function Arguments","file":"apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts","refactoring-examples":null,"change-level":"notice","is-hotspot?":false,"what-changed":"The ratio of strings in function arguments increases from 71.43% to 76.92%, threshold = 39.0%","how-to-fix":"Heavy string usage indicates a missing domain language. Introduce data types that encapsulate the semantics. For example, a user_name is better represented as a constrained User type rather than a pure string, which could be anything.","change-type":"degraded"}]},"external-review-provider":"GitHub"},"analysistime":"2024-01-16T23:44:55.000Z","project-name":"clients","repository":"https://github.com/bitwarden/clients.git"}}