1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-03 10:13:31 +00:00

Merge branch 'main' into km/encstring-remove-decrypt

This commit is contained in:
Bernd Schoolmann
2025-10-27 18:05:23 +01:00
committed by GitHub
234 changed files with 1795 additions and 476 deletions

View File

@@ -0,0 +1,25 @@
Please review this pull request with a focus on:
- Code quality and best practices
- Potential bugs or issues
- Security implications
- Performance considerations
Note: The PR branch is already checked out in the current working directory.
Provide a comprehensive review including:
- Summary of changes since last review
- Critical issues found (be thorough)
- Suggested improvements (be thorough)
- Good practices observed (be concise - list only the most notable items without elaboration)
- Action items for the author
- Leverage collapsible <details> sections where appropriate for lengthy explanations or code snippets to enhance human readability
When reviewing subsequent commits:
- Track status of previously identified issues (fixed/unfixed/reopened)
- Identify NEW problems introduced since last review
- Note if fixes introduced new issues
IMPORTANT: Be comprehensive about issues and improvements. For good practices, be brief - just note what was done well without explaining why or praising excessively.

2
.github/CODEOWNERS vendored
View File

@@ -30,7 +30,7 @@ libs/common/src/auth @bitwarden/team-auth-dev
apps/browser/src/tools @bitwarden/team-tools-dev
apps/cli/src/tools @bitwarden/team-tools-dev
apps/desktop/src/app/tools @bitwarden/team-tools-dev
apps/desktop/desktop_native/bitwarden_chromium_importer @bitwarden/team-tools-dev
apps/desktop/desktop_native/chromium_importer @bitwarden/team-tools-dev
apps/web/src/app/tools @bitwarden/team-tools-dev
libs/angular/src/tools @bitwarden/team-tools-dev
libs/common/src/models/export @bitwarden/team-tools-dev

View File

@@ -139,6 +139,7 @@
"@babel/core",
"@babel/preset-env",
"@bitwarden/sdk-internal",
"@bitwarden/commercial-sdk-internal",
"@electron/fuses",
"@electron/notarize",
"@electron/rebuild",

View File

@@ -219,12 +219,14 @@ jobs:
archive_name_prefix: ""
npm_command_prefix: "dist:"
readable: "open source license"
type: "oss"
- build_prefix: "bit-"
artifact_prefix: "bit-"
source_archive_name_prefix: "bit-"
archive_name_prefix: "bit-"
npm_command_prefix: "dist:bit:"
readable: "commercial license"
type: "commercial"
browser:
- name: "chrome"
npm_command_suffix: "chrome"
@@ -279,6 +281,11 @@ jobs:
run: npm ci
working-directory: browser-source/
- name: Remove commercial packages
if: ${{ matrix.license_type.type == 'oss' }}
run: rm -rf node_modules/@bitwarden/commercial-sdk-internal
working-directory: browser-source/
- name: Download SDK artifacts
if: ${{ inputs.sdk_branch != '' }}
uses: bitwarden/gh-actions/download-artifacts@main
@@ -350,11 +357,13 @@ jobs:
archive_name_prefix: ""
npm_command_prefix: "dist:"
readable: "open source license"
type: "oss"
- build_prefix: "bit-"
artifact_prefix: "bit-"
archive_name_prefix: "bit-"
npm_command_prefix: "dist:bit:"
readable: "commercial license"
type: "commercial"
env:
_BUILD_NUMBER: ${{ needs.setup.outputs.adj_build_number }}
_NODE_VERSION: ${{ needs.setup.outputs.node_version }}
@@ -461,6 +470,11 @@ jobs:
run: npm ci
working-directory: ./
- name: Remove commercial packages
if: ${{ matrix.license_type.type == 'oss' }}
run: rm -rf node_modules/@bitwarden/commercial-sdk-internal
working-directory: ./
- name: Download SDK Artifacts
if: ${{ inputs.sdk_branch != '' }}
uses: bitwarden/gh-actions/download-artifacts@main

View File

@@ -98,8 +98,8 @@ jobs:
]
license_type:
[
{ build_prefix: "oss", artifact_prefix: "-oss", readable: "open source license" },
{ build_prefix: "bit", artifact_prefix: "", readable: "commercial license" }
{ type: "oss", build_prefix: "oss", artifact_prefix: "-oss", readable: "open source license" },
{ type: "commercial", build_prefix: "bit", artifact_prefix: "", readable: "commercial license" }
]
runs-on: ${{ matrix.os.distro }}
needs: setup
@@ -140,6 +140,11 @@ jobs:
run: npm ci
working-directory: ./
- name: Remove commercial packages
if: ${{ matrix.license_type.type == 'oss' }}
run: rm -rf node_modules/@bitwarden/commercial-sdk-internal
working-directory: ./
- name: Download SDK Artifacts
if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }}
uses: bitwarden/gh-actions/download-artifacts@main
@@ -291,8 +296,8 @@ jobs:
matrix:
license_type:
[
{ build_prefix: "oss", artifact_prefix: "-oss", readable: "open source license" },
{ build_prefix: "bit", artifact_prefix: "", readable: "commercial license" }
{ type: "oss", build_prefix: "oss", artifact_prefix: "-oss", readable: "open source license" },
{ type: "commercial", build_prefix: "bit", artifact_prefix: "", readable: "commercial license" }
]
runs-on: windows-2022
permissions:
@@ -410,6 +415,11 @@ jobs:
run: npm ci
working-directory: ./
- name: Remove commercial packages
if: ${{ matrix.license_type.type == 'oss' }}
run: Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "node_modules/@bitwarden/commercial-sdk-internal"
working-directory: ./
- name: Download SDK Artifacts
if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }}
uses: bitwarden/gh-actions/download-artifacts@main

View File

@@ -99,34 +99,43 @@ jobs:
matrix:
include:
- artifact_name: selfhosted-open-source
license_type: "oss"
image_name: web-oss
npm_command: dist:oss:selfhost
- artifact_name: cloud-COMMERCIAL
license_type: "commercial"
image_name: web-cloud
npm_command: dist:bit:cloud
- artifact_name: selfhosted-COMMERCIAL
license_type: "commercial"
image_name: web
npm_command: dist:bit:selfhost
- artifact_name: selfhosted-DEV
license_type: "commercial"
image_name: web
npm_command: build:bit:selfhost:dev
git_metadata: true
- artifact_name: cloud-QA
license_type: "commercial"
image_name: web-qa-cloud
npm_command: build:bit:qa
git_metadata: true
- artifact_name: ee
license_type: "commercial"
image_name: web-ee
npm_command: build:bit:ee
git_metadata: true
- artifact_name: cloud-euprd
license_type: "commercial"
image_name: web-euprd
npm_command: build:bit:euprd
- artifact_name: cloud-euqa
license_type: "commercial"
image_name: web-euqa
npm_command: build:bit:euqa
git_metadata: true
- artifact_name: cloud-usdev
license_type: "commercial"
image_name: web-usdev
npm_command: build:bit:usdev
git_metadata: true
@@ -269,6 +278,7 @@ jobs:
build-args: |
NODE_VERSION=${{ env._NODE_VERSION }}
NPM_COMMAND=${{ matrix.npm_command }}
LICENSE_TYPE=${{ matrix.license_type }}
context: .
file: apps/web/Dockerfile
load: true

View File

@@ -75,6 +75,9 @@ jobs:
- name: Lint unowned dependencies
run: npm run lint:dep-ownership
- name: Lint sdk-internal versions
run: npm run lint:sdk-internal-versions
- name: Run linter
run: npm run lint

View File

@@ -1,124 +1,20 @@
name: Review code
name: Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
types: [opened, synchronize, reopened, ready_for_review]
permissions: {}
jobs:
review:
name: Review
runs-on: ubuntu-24.04
uses: bitwarden/gh-actions/.github/workflows/_review-code.yml@main
secrets:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
permissions:
contents: read
id-token: write
pull-requests: write
steps:
- name: Check out repo
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
persist-credentials: false
- name: Check for Vault team changes
id: check_changes
run: |
# Ensure we have the base branch
git fetch origin ${{ github.base_ref }}
echo "Comparing changes between origin/${{ github.base_ref }} and HEAD"
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
if [ -z "$CHANGED_FILES" ]; then
echo "Zero files changed"
echo "vault_team_changes=false" >> $GITHUB_OUTPUT
exit 0
fi
# Handle variations in spacing and multiple teams
VAULT_PATTERNS=$(grep -E "@bitwarden/team-vault-dev(\s|$)" .github/CODEOWNERS 2>/dev/null | awk '{print $1}')
if [ -z "$VAULT_PATTERNS" ]; then
echo "⚠️ No patterns found for @bitwarden/team-vault-dev in CODEOWNERS"
echo "vault_team_changes=false" >> $GITHUB_OUTPUT
exit 0
fi
vault_team_changes=false
for pattern in $VAULT_PATTERNS; do
echo "Checking pattern: $pattern"
# Handle **/directory patterns
if [[ "$pattern" == "**/"* ]]; then
# Remove the **/ prefix
dir_pattern="${pattern#\*\*/}"
# Check if any file contains this directory in its path
if echo "$CHANGED_FILES" | grep -qE "(^|/)${dir_pattern}(/|$)"; then
vault_team_changes=true
echo "✅ Found files matching pattern: $pattern"
echo "$CHANGED_FILES" | grep -E "(^|/)${dir_pattern}(/|$)" | sed 's/^/ - /'
break
fi
else
# Handle other patterns (shouldn't happen based on your CODEOWNERS)
if echo "$CHANGED_FILES" | grep -q "$pattern"; then
vault_team_changes=true
echo "✅ Found files matching pattern: $pattern"
echo "$CHANGED_FILES" | grep "$pattern" | sed 's/^/ - /'
break
fi
fi
done
echo "vault_team_changes=$vault_team_changes" >> $GITHUB_OUTPUT
if [ "$vault_team_changes" = "true" ]; then
echo ""
echo "✅ Vault team changes detected - proceeding with review"
else
echo ""
echo "❌ No Vault team changes detected - skipping review"
fi
- name: Review with Claude Code
if: steps.check_changes.outputs.vault_team_changes == 'true'
uses: anthropics/claude-code-action@ac1a3207f3f00b4a37e2f3a6f0935733c7c64651 # v1.0.11
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
track_progress: true
use_sticky_comment: true
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
TITLE: ${{ github.event.pull_request.title }}
BODY: ${{ github.event.pull_request.body }}
AUTHOR: ${{ github.event.pull_request.user.login }}
COMMIT: ${{ github.event.pull_request.head.sha }}
Please review this pull request with a focus on:
- Code quality and best practices
- Potential bugs or issues
- Security implications
- Performance considerations
Note: The PR branch is already checked out in the current working directory.
Provide a comprehensive review including:
- Summary of changes since last review
- Critical issues found (be thorough)
- Suggested improvements (be thorough)
- Good practices observed (be concise - list only the most notable items without elaboration)
- Action items for the author
- Leverage collapsible <details> sections where appropriate for lengthy explanations or code snippets to enhance human readability
When reviewing subsequent commits:
- Track status of previously identified issues (fixed/unfixed/reopened)
- Identify NEW problems introduced since last review
- Note if fixes introduced new issues
IMPORTANT: Be comprehensive about issues and improvements. For good practices, be brief - just note what was done well without explaining why or praising excessively.
claude_args: |
--allowedTools "mcp__github_comment__update_claude_comment,mcp__github_inline_comment__create_inline_comment,Bash(gh pr diff:*),Bash(gh pr view:*)"

1
.gitignore vendored
View File

@@ -10,7 +10,6 @@ Thumbs.db
*.launch
.settings/
*.sublime-workspace
.claude
.serena
# Visual Studio Code

2
.npmrc
View File

@@ -1,4 +1,4 @@
save-exact=true
# Increase available heap size to avoid running out of memory when compiling.
# This applies to all npm scripts in this repository.
node-options=--max-old-space-size=8192
node-options=--max-old-space-size=8192

View File

@@ -5721,5 +5721,11 @@
"settingDisabledByPolicy": {
"message": "This setting is disabled by your organization's policy.",
"description": "This hint text is displayed when a user setting is disabled due to an organization policy."
},
"zipPostalCodeLabel": {
"message": "ZIP / Postal code"
},
"cardNumberLabel": {
"message": "Card number"
}
}

View File

@@ -7,12 +7,16 @@ import { IconButtonModule } from "@bitwarden/components";
import BrowserPopupUtils from "../../browser/browser-popup-utils";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-pop-out",
templateUrl: "pop-out.component.html",
imports: [CommonModule, JslibModule, IconButtonModule],
})
export class PopOutComponent implements OnInit {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() show = true;
constructor(private platformUtilsService: PlatformUtilsService) {}

View File

@@ -13,6 +13,8 @@ import { PopupRouterCacheService, popupRouterCacheGuard } from "./popup-router-c
const flushPromises = async () => await new Promise(process.nextTick);
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
template: "",
standalone: false,

View File

@@ -19,12 +19,16 @@ import {
import { PopupViewCacheService } from "./popup-view-cache.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
template: "",
standalone: false,
})
export class EmptyComponent {}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
template: "",
standalone: false,

View File

@@ -10,6 +10,8 @@ import { AnchorLinkDirective, CalloutModule, BannerModule } from "@bitwarden/com
import { I18nPipe } from "@bitwarden/ui-common";
import { AtRiskPasswordCalloutData, AtRiskPasswordCalloutService } from "@bitwarden/vault";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "vault-at-risk-password-callout",
imports: [

View File

@@ -17,6 +17,8 @@ export const AtRiskCarouselDialogResult = {
type AtRiskCarouselDialogResult = UnionOfValues<typeof AtRiskCarouselDialogResult>;
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "vault-at-risk-carousel-dialog",
templateUrl: "./at-risk-carousel-dialog.component.html",
@@ -32,6 +34,8 @@ type AtRiskCarouselDialogResult = UnionOfValues<typeof AtRiskCarouselDialogResul
export class AtRiskCarouselDialogComponent {
private dialogRef = inject(DialogRef);
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
protected dismissBtnEnabled = signal(false);
protected async dismiss() {

View File

@@ -37,28 +37,42 @@ import { AtRiskCarouselDialogResult } from "../at-risk-carousel-dialog/at-risk-c
import { AtRiskPasswordPageService } from "./at-risk-password-page.service";
import { AtRiskPasswordsComponent } from "./at-risk-passwords.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "popup-header",
template: `<ng-content></ng-content>`,
})
class MockPopupHeaderComponent {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() pageTitle: string | undefined;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() backAction: (() => void) | undefined;
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "popup-page",
template: `<ng-content></ng-content>`,
})
class MockPopupPageComponent {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() loading: boolean | undefined;
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-vault-icon",
template: `<ng-content></ng-content>`,
})
class MockAppIcon {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() cipher: CipherView | undefined;
}

View File

@@ -58,6 +58,8 @@ import {
import { AtRiskPasswordPageService } from "./at-risk-password-page.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
imports: [
PopupPageComponent,

View File

@@ -131,6 +131,8 @@ class QueryParams {
export type AddEditQueryParams = Partial<Record<keyof QueryParams, string>>;
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-add-edit-v2",
templateUrl: "add-edit-v2.component.html",

View File

@@ -28,6 +28,8 @@ import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup
import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-assign-collections",
templateUrl: "./assign-collections.component.html",

View File

@@ -25,20 +25,30 @@ import { PopupRouterCacheService } from "../../../../../platform/popup/view-cach
import { AttachmentsV2Component } from "./attachments-v2.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "popup-header",
template: `<ng-content></ng-content>`,
})
class MockPopupHeaderComponent {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() pageTitle: string;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() backAction: () => void;
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "popup-footer",
template: `<ng-content></ng-content>`,
})
class MockPopupFooterComponent {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() pageTitle: string;
}

View File

@@ -17,6 +17,8 @@ import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup
import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component";
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-attachments-v2",
templateUrl: "./attachments-v2.component.html",

View File

@@ -25,6 +25,8 @@ import { CipherFormContainer } from "@bitwarden/vault";
import BrowserPopupUtils from "../../../../../../platform/browser/browser-popup-utils";
import { FilePopoutUtilsService } from "../../../../../../tools/popup/services/file-popout-utils.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-open-attachments",
templateUrl: "./open-attachments.component.html",
@@ -39,6 +41,8 @@ import { FilePopoutUtilsService } from "../../../../../../tools/popup/services/f
})
export class OpenAttachmentsComponent implements OnInit {
/** Cipher `id` */
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input({ required: true }) cipherId: CipherId;
/** True when the attachments window should be opened in a popout */

View File

@@ -15,6 +15,8 @@ import { VaultPopupItemsService } from "../../../services/vault-popup-items.serv
import { PopupCipherViewLike } from "../../../views/popup-cipher.view";
import { VaultListItemsContainerComponent } from "../vault-list-items-container/vault-list-items-container.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
imports: [
CommonModule,
@@ -46,7 +48,7 @@ export class AutofillVaultListItemsComponent {
startWith(true), // Start with true to avoid flashing the fill button on first load
);
protected groupByType = toSignal(
protected readonly groupByType = toSignal(
this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => !hasFilter)),
);

View File

@@ -15,6 +15,8 @@ import { VaultPopupAutofillService } from "../../../services/vault-popup-autofil
const blockedURISettingsRoute = "/blocked-domains";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
imports: [
BannerModule,

View File

@@ -9,6 +9,8 @@ import { VaultCarouselModule } from "@bitwarden/vault";
import { IntroCarouselService } from "../../../services/intro-carousel.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-intro-carousel",
templateUrl: "./intro-carousel.component.html",

View File

@@ -21,6 +21,8 @@ type CipherItem = {
field: CopyAction;
};
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-item-copy-actions",
templateUrl: "item-copy-actions.component.html",
@@ -35,6 +37,8 @@ type CipherItem = {
})
export class ItemCopyActionsComponent {
protected showQuickCopyActions$ = inject(VaultPopupCopyButtonsService).showQuickCopyActions$;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input({ required: true }) cipher!: CipherViewLike;
protected CipherViewLikeUtils = CipherViewLikeUtils;

View File

@@ -34,6 +34,8 @@ import { PasswordRepromptService } from "@bitwarden/vault";
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
import { AddEditQueryParams } from "../add-edit/add-edit-v2.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-item-more-options",
templateUrl: "./item-more-options.component.html",
@@ -42,6 +44,8 @@ import { AddEditQueryParams } from "../add-edit/add-edit-v2.component";
export class ItemMoreOptionsComponent {
private _cipher$ = new BehaviorSubject<CipherViewLike>(undefined);
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input({
required: true,
})
@@ -57,6 +61,8 @@ export class ItemMoreOptionsComponent {
* Flag to show view item menu option. Used when something else is
* assigned as the primary action for the item, such as autofill.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input({ transform: booleanAttribute })
showViewOption: boolean;
@@ -64,6 +70,8 @@ export class ItemMoreOptionsComponent {
* Flag to hide the autofill menu options. Used for items that are
* already in the autofill list suggestion.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input({ transform: booleanAttribute })
hideAutofillOptions: boolean;

View File

@@ -23,6 +23,8 @@ export interface NewItemInitialValues {
collectionId?: CollectionId;
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-new-item-dropdown",
templateUrl: "new-item-dropdown-v2.component.html",
@@ -34,6 +36,8 @@ export class NewItemDropdownV2Component implements OnInit {
/**
* Optional initial values to pass to the add cipher form
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input()
initialValues: NewItemInitialValues;

View File

@@ -18,14 +18,24 @@ import {
VaultGeneratorDialogComponent,
} from "./vault-generator-dialog.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "vault-cipher-form-generator",
template: "",
})
class MockCipherFormGenerator {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() type: "password" | "username" = "password";
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
@Output() algorithmSelected: EventEmitter<AlgorithmInfo> = new EventEmitter();
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() uri: string = "";
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
@Output() valueGenerated = new EventEmitter<string>();
}

View File

@@ -38,6 +38,8 @@ export const GeneratorDialogAction = {
type GeneratorDialogAction = UnionOfValues<typeof GeneratorDialogAction>;
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-vault-generator-dialog",
templateUrl: "./vault-generator-dialog.component.html",

View File

@@ -17,6 +17,8 @@ import { VaultPopupListFiltersService } from "../../../../../vault/popup/service
import { VaultListFiltersComponent } from "../vault-list-filters/vault-list-filters.component";
import { VaultV2SearchComponent } from "../vault-search/vault-v2-search.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-vault-header-v2",
templateUrl: "vault-header-v2.component.html",
@@ -31,6 +33,8 @@ import { VaultV2SearchComponent } from "../vault-search/vault-v2-search.componen
],
})
export class VaultHeaderV2Component {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@ViewChild(DisclosureComponent) disclosure: DisclosureComponent;
/** Emits the visibility status of the disclosure component. */

View File

@@ -8,6 +8,8 @@ import { ChipSelectComponent } from "@bitwarden/components";
import { VaultPopupListFiltersService } from "../../../services/vault-popup-list-filters.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-vault-list-filters",
templateUrl: "./vault-list-filters.component.html",

View File

@@ -90,12 +90,18 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
private vaultPopupSectionService = inject(VaultPopupSectionService);
protected CipherViewLikeUtils = CipherViewLikeUtils;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@ViewChild(CdkVirtualScrollViewport, { static: false }) viewPort!: CdkVirtualScrollViewport;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@ViewChild(DisclosureComponent) disclosure!: DisclosureComponent;
/**
* Indicates whether the section should be open or closed if collapsibleKey is provided
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
protected sectionOpenState: Signal<boolean> = computed(() => {
if (!this.collapsibleKey()) {
return true;
@@ -130,17 +136,23 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
*/
private viewCipherTimeout?: number;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
ciphers = input<PopupCipherViewLike[]>([]);
/**
* If true, we will group ciphers by type (Login, Card, Identity)
* within subheadings in a single container, converted to a WritableSignal.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
groupByType = input<boolean | undefined>(false);
/**
* Computed signal for a grouped list of ciphers with an optional header
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
cipherGroups = computed<
{
subHeaderKey?: string;
@@ -183,6 +195,8 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
/**
* Title for the vault list item section.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
title = input<string | undefined>(undefined);
/**
@@ -191,33 +205,45 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
* The key must be added to the state definition in `vault-popup-section.service.ts` since the
* collapsed state is stored locally.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
collapsibleKey = input<keyof PopupSectionOpen | undefined>(undefined);
/**
* Optional description for the vault list item section. Will be shown below the title even when
* no ciphers are available.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
description = input<string | undefined>(undefined);
/**
* Option to show a refresh button in the section header.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
showRefresh = input(false, { transform: booleanAttribute });
/**
* Event emitted when the refresh button is clicked.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
@Output()
onRefresh = new EventEmitter<void>();
/**
* Flag indicating that the current tab location is blocked
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
currentURIIsBlocked = toSignal(this.vaultPopupAutofillService.currentTabIsOnBlocklist$);
/**
* Resolved i18n key to use for suggested cipher items
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
cipherItemTitleKey = computed(() => {
return (cipher: CipherViewLike) => {
const login = CipherViewLikeUtils.getLogin(cipher);
@@ -233,11 +259,15 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
/**
* Option to show the autofill button for each item.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
showAutofillButton = input(false, { transform: booleanAttribute });
/**
* Flag indicating whether the suggested cipher item autofill button should be shown or not
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
hideAutofillButton = computed(
() => !this.showAutofillButton() || this.currentURIIsBlocked() || this.primaryActionAutofill(),
);
@@ -245,22 +275,30 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
/**
* Flag indicating whether the cipher item autofill menu options should be shown or not
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
hideAutofillMenuOptions = computed(() => this.currentURIIsBlocked() || this.showAutofillButton());
/**
* Option to perform autofill operation as the primary action for autofill suggestions.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
primaryActionAutofill = input(false, { transform: booleanAttribute });
/**
* Remove the bottom margin from the bit-section in this component
* (used for containers at the end of the page where bottom margin is not needed)
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
disableSectionMargin = input(false, { transform: booleanAttribute });
/**
* Remove the description margin
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
disableDescriptionMargin = input(false, { transform: booleanAttribute });
/**
@@ -275,6 +313,8 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
return collections[0]?.name;
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
protected autofillShortcutTooltip = signal<string | undefined>(undefined);
constructor(

View File

@@ -18,6 +18,8 @@ import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup
import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component";
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "vault-password-history-v2",
templateUrl: "vault-password-history-v2.component.html",

View File

@@ -10,6 +10,8 @@ import { SearchModule } from "@bitwarden/components";
import { VaultPopupItemsService } from "../../../services/vault-popup-items.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
imports: [CommonModule, SearchModule, JslibModule, FormsModule],
selector: "app-vault-v2-search",

View File

@@ -64,6 +64,8 @@ const VaultState = {
type VaultState = UnionOfValues<typeof VaultState>;
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-vault",
templateUrl: "vault-v2.component.html",
@@ -89,6 +91,8 @@ type VaultState = UnionOfValues<typeof VaultState>;
],
})
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
NudgeType = NudgeType;

View File

@@ -76,6 +76,8 @@ type LoadAction =
| typeof COPY_VERIFICATION_CODE_ID
| typeof UPDATE_PASSWORD;
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-view-v2",
templateUrl: "view-v2.component.html",

View File

@@ -31,7 +31,7 @@ export class VaultPopupSectionService {
private vaultPopupItemsService = inject(VaultPopupItemsService);
private stateProvider = inject(StateProvider);
private hasFilterOrSearchApplied = toSignal(
private readonly hasFilterOrSearchApplied = toSignal(
this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => hasFilter)),
);
@@ -40,7 +40,7 @@ export class VaultPopupSectionService {
* application-applied overrides.
* `null` means there is no current override
*/
private temporaryStateOverride = signal<Partial<PopupSectionOpen> | null>(null);
private readonly temporaryStateOverride = signal<Partial<PopupSectionOpen> | null>(null);
constructor() {
effect(
@@ -71,7 +71,7 @@ export class VaultPopupSectionService {
* Stored disk state for the open/close state of the sections, with an initial value provided
* if the stored disk state does not yet exist.
*/
private sectionOpenStoredState = toSignal<PopupSectionOpen | null>(
private readonly sectionOpenStoredState = toSignal<PopupSectionOpen | null>(
this.sectionOpenStateProvider.state$.pipe(map((sectionOpen) => sectionOpen ?? INITIAL_OPEN)),
// Indicates that the state value is loading
{ initialValue: null },
@@ -81,7 +81,7 @@ export class VaultPopupSectionService {
* Indicates the current open/close display state of each section, accounting for temporary
* non-persisted overrides.
*/
sectionOpenDisplayState: Signal<Partial<PopupSectionOpen>> = computed(() => ({
readonly sectionOpenDisplayState: Signal<Partial<PopupSectionOpen>> = computed(() => ({
...this.sectionOpenStoredState(),
...this.temporaryStateOverride(),
}));

View File

@@ -22,20 +22,30 @@ import { VaultPopupCopyButtonsService } from "../services/vault-popup-copy-butto
import { AppearanceV2Component } from "./appearance-v2.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "popup-header",
template: `<ng-content></ng-content>`,
})
class MockPopupHeaderComponent {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() pageTitle: string;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() backAction: () => void;
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "popup-page",
template: `<ng-content></ng-content>`,
})
class MockPopupPageComponent {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() loading: boolean;
}

View File

@@ -33,6 +33,8 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
import { PopupSizeService } from "../../../platform/popup/layout/popup-size.service";
import { VaultPopupCopyButtonsService } from "../services/vault-popup-copy-buttons.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
templateUrl: "./appearance-v2.component.html",
imports: [

View File

@@ -33,6 +33,8 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
templateUrl: "archive.component.html",
standalone: true,

View File

@@ -13,6 +13,8 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
templateUrl: "download-bitwarden.component.html",
imports: [

View File

@@ -21,20 +21,30 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade
import { FoldersV2Component } from "./folders-v2.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "popup-header",
template: `<ng-content></ng-content>`,
})
class MockPopupHeaderComponent {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() pageTitle: string = "";
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() backAction: () => void = () => {};
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "popup-footer",
template: `<ng-content></ng-content>`,
})
class MockPopupFooterComponent {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() pageTitle: string = "";
}

View File

@@ -22,6 +22,8 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
templateUrl: "./folders-v2.component.html",
imports: [

View File

@@ -17,6 +17,8 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
templateUrl: "more-from-bitwarden-page-v2.component.html",
imports: [

View File

@@ -53,9 +53,13 @@ export class TrashListItemsContainerComponent {
/**
* The list of trashed items to display.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input()
ciphers: PopupCipherViewLike[] = [];
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input()
headerText: string;

View File

@@ -19,6 +19,8 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
templateUrl: "vault-settings-v2.component.html",
imports: [
@@ -37,12 +39,12 @@ export class VaultSettingsV2Component implements OnInit, OnDestroy {
private userId$ = this.accountService.activeAccount$.pipe(getUserId);
// Check if user is premium user, they will be able to archive items
protected userCanArchive = toSignal(
protected readonly userCanArchive = toSignal(
this.userId$.pipe(switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId))),
);
// Check if user has archived items (does not check if user is premium)
protected showArchiveFilter = toSignal(
protected readonly showArchiveFilter = toSignal(
this.userId$.pipe(switchMap((userId) => this.cipherArchiveService.showArchiveVault$(userId))),
);

View File

@@ -36,7 +36,8 @@ const DEFAULT_PARAMS = {
* outputPath?: string;
* mode?: string;
* env?: string;
* additionalEntries?: { [outputPath: string]: string }
* additionalEntries?: { [outputPath: string]: string };
* importAliases?: import("webpack").ResolveOptions["alias"];
* }} params - The input parameters for building the config.
*/
module.exports.buildConfig = function buildConfig(params) {
@@ -362,6 +363,7 @@ module.exports.buildConfig = function buildConfig(params) {
path: require.resolve("path-browserify"),
},
cache: true,
alias: params.importAliases,
},
output: {
filename: "[name].js",
@@ -482,6 +484,7 @@ module.exports.buildConfig = function buildConfig(params) {
path: require.resolve("path-browserify"),
},
cache: true,
alias: params.importAliases,
},
dependencies: ["main"],
plugins: [...requiredPlugins, new AngularCheckPlugin()],

View File

@@ -31,6 +31,7 @@ const DEFAULT_PARAMS = {
* localesPath?: string;
* externalsModulesDir?: string;
* watch?: boolean;
* importAliases?: import("webpack").ResolveOptions["alias"];
* }} params
*/
module.exports.buildConfig = function buildConfig(params) {
@@ -95,6 +96,7 @@ module.exports.buildConfig = function buildConfig(params) {
symlinks: false,
modules: params.modulesPath,
plugins: [new TsconfigPathsPlugin({ configFile: params.tsConfig })],
alias: params.importAliases,
},
output: {
filename: "[name].js",

View File

@@ -440,33 +440,6 @@ dependencies = [
"tokio-util",
]
[[package]]
name = "bitwarden_chromium_importer"
version = "0.0.0"
dependencies = [
"aes",
"aes-gcm",
"anyhow",
"async-trait",
"base64",
"cbc",
"hex",
"homedir",
"napi",
"napi-derive",
"oo7",
"pbkdf2",
"rand 0.9.1",
"rusqlite",
"security-framework",
"serde",
"serde_json",
"sha1",
"tokio",
"winapi",
"windows 0.61.1",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -606,6 +579,31 @@ dependencies = [
"zeroize",
]
[[package]]
name = "chromium_importer"
version = "0.0.0"
dependencies = [
"aes",
"aes-gcm",
"anyhow",
"async-trait",
"base64",
"cbc",
"hex",
"homedir",
"oo7",
"pbkdf2",
"rand 0.9.1",
"rusqlite",
"security-framework",
"serde",
"serde_json",
"sha1",
"tokio",
"winapi",
"windows 0.61.1",
]
[[package]]
name = "cipher"
version = "0.4.4"
@@ -968,7 +966,7 @@ dependencies = [
"anyhow",
"autotype",
"base64",
"bitwarden_chromium_importer",
"chromium_importer",
"desktop_core",
"hex",
"napi",

View File

@@ -2,7 +2,7 @@
resolver = "2"
members = [
"autotype",
"bitwarden_chromium_importer",
"chromium_importer",
"core",
"macos_provider",
"napi",
@@ -68,7 +68,7 @@ tokio = "=1.45.0"
tokio-stream = "=0.1.15"
tokio-util = "=0.7.13"
tracing = "=0.1.41"
tracing-subscriber = { version = "=0.3.20", features = ["fmt", "env-filter"] }
tracing-subscriber = { version = "=0.3.20", features = ["fmt", "env-filter", "tracing-log"] }
typenum = "=1.18.0"
uniffi = "=0.28.3"
widestring = "=1.2.0"

View File

@@ -1,48 +0,0 @@
//! Cryptographic primitives used in the SDK
use anyhow::{anyhow, Result};
use aes::cipher::{
block_padding::Pkcs7, generic_array::GenericArray, typenum::U32, BlockDecryptMut, KeyIvInit,
};
pub fn decrypt_aes256(iv: &[u8; 16], data: &[u8], key: GenericArray<u8, U32>) -> Result<Vec<u8>> {
let iv = GenericArray::from_slice(iv);
let mut data = data.to_vec();
cbc::Decryptor::<aes::Aes256>::new(&key, iv)
.decrypt_padded_mut::<Pkcs7>(&mut data)
.map_err(|_| anyhow!("Failed to decrypt data"))?;
Ok(data)
}
#[cfg(test)]
mod tests {
use aes::cipher::{
generic_array::{sequence::GenericSequence, GenericArray},
ArrayLength,
};
use base64::{engine::general_purpose::STANDARD, Engine};
pub fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> {
(0..length).map(|i| offset + i as u8 * increment).collect()
}
pub fn generate_generic_array<N: ArrayLength<u8>>(
offset: u8,
increment: u8,
) -> GenericArray<u8, N> {
GenericArray::generate(|i| offset + i as u8 * increment)
}
#[test]
fn test_decrypt_aes256() {
let iv = generate_vec(16, 0, 1);
let iv: &[u8; 16] = iv.as_slice().try_into().unwrap();
let key = generate_generic_array(0, 1);
let data: Vec<u8> = STANDARD.decode("ByUF8vhyX4ddU9gcooznwA==").unwrap();
let decrypted = super::decrypt_aes256(iv, &data, key).unwrap();
assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!\u{6}\u{6}\u{6}\u{6}\u{6}\u{6}");
}
}

View File

@@ -1,8 +0,0 @@
#[macro_use]
extern crate napi_derive;
pub mod chromium;
pub mod metadata;
pub mod util;
pub use crate::chromium::platform::SUPPORTED_BROWSERS as PLATFORM_SUPPORTED_BROWSERS;

View File

@@ -1,5 +1,5 @@
[package]
name = "bitwarden_chromium_importer"
name = "chromium_importer"
edition = { workspace = true }
license = { workspace = true }
version = { workspace = true }
@@ -14,8 +14,6 @@ base64 = { workspace = true }
cbc = { workspace = true, features = ["alloc"] }
hex = { workspace = true }
homedir = { workspace = true }
napi = { workspace = true }
napi-derive = { workspace = true }
pbkdf2 = "=0.12.2"
rand = { workspace = true }
rusqlite = { version = "=0.37.0", features = ["bundled"] }
@@ -36,4 +34,3 @@ oo7 = { workspace = true }
[lints]
workspace = true

View File

@@ -1,6 +1,13 @@
# Windows ABE Architecture
# Chromium Direct Importer
## Overview
A rust library that allows you to directly import credentials from Chromium-based browsers.
## Windows ABE Architecture
On Windows chrome has additional protection measurements which needs to be circumvented in order to
get access to the passwords.
### Overview
The Windows Application Bound Encryption (ABE) consists of three main components that work together:
@@ -10,7 +17,7 @@ The Windows Application Bound Encryption (ABE) consists of three main components
_(The names of the binaries will be changed for the released product.)_
## The goal
### The goal
The goal of this subsystem is to decrypt the master encryption key with which the login information
is encrypted on the local system in Windows. This applies to the most recent versions of Chrome and
@@ -24,7 +31,7 @@ Protection API at the system level on top of that. This triply encrypted key is
The next paragraphs describe what is done at each level to decrypt the key.
## 1. Client library
### 1. Client library
This is a Rust module that is part of the Chromium importer. It only compiles and runs on Windows
(see `abe.rs` and `abe_config.rs`). Its main task is to launch `admin.exe` with elevated privileges
@@ -52,7 +59,7 @@ admin.exe --service-exe "c:\temp\service.exe" --encrypted "QVBQQgEAAADQjJ3fARXRE
**At this point, the user must permit the action to be performed on the UAC screen.**
## 2. Admin executable
### 2. Admin executable
This executable receives the full path of `service.exe` and the data to be decrypted.
@@ -67,7 +74,7 @@ is sent to the named pipe server created by the user. The user responds with `ok
After that, the executable stops and uninstalls the service and then exits.
## 3. System service
### 3. System service
The service starts and creates a named pipe server for communication between `admin.exe` and the
system service. Please note that it is not possible to communicate between the user and the system
@@ -83,7 +90,7 @@ removed from the system. Even though we send only one request, the service is de
many clients with as many messages as needed and could be installed on the system permanently if
necessary.
## 4. Back to client library
### 4. Back to client library
The decrypted base64-encoded string comes back from the admin executable to the named pipe server at
the user level. At this point, it has been decrypted only once at the system level.
@@ -99,7 +106,7 @@ itself), it's either AES-256-GCM or ChaCha20Poly1305 encryption scheme. The deta
After all of these steps, we have the master key which can be used to decrypt the password
information stored in the local database.
## Summary
### Summary
The Windows ABE decryption process involves a three-tier architecture with named pipe communication:

View File

@@ -7,11 +7,9 @@ use hex::decode;
use homedir::my_home;
use rusqlite::{params, Connection};
// Platform-specific code
#[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(target_os = "windows", path = "windows.rs")]
#[cfg_attr(target_os = "macos", path = "macos.rs")]
pub mod platform;
mod platform;
pub(crate) use platform::SUPPORTED_BROWSERS as PLATFORM_SUPPORTED_BROWSERS;
//
// Public API
@@ -22,10 +20,7 @@ pub struct ProfileInfo {
pub name: String,
pub folder: String,
#[allow(dead_code)]
pub account_name: Option<String>,
#[allow(dead_code)]
pub account_email: Option<String>,
}
@@ -113,12 +108,12 @@ pub async fn import_logins(
//
#[derive(Debug, Clone, Copy)]
pub struct BrowserConfig {
pub(crate) struct BrowserConfig {
pub name: &'static str,
pub data_dir: &'static str,
}
pub static SUPPORTED_BROWSER_MAP: LazyLock<
pub(crate) static SUPPORTED_BROWSER_MAP: LazyLock<
std::collections::HashMap<&'static str, &'static BrowserConfig>,
> = LazyLock::new(|| {
platform::SUPPORTED_BROWSERS
@@ -140,12 +135,12 @@ fn get_browser_data_dir(config: &BrowserConfig) -> Result<PathBuf> {
//
#[async_trait]
pub trait CryptoService: Send {
pub(crate) trait CryptoService: Send {
async fn decrypt_to_string(&mut self, encrypted: &[u8]) -> Result<String>;
}
#[derive(serde::Deserialize, Clone)]
pub struct LocalState {
pub(crate) struct LocalState {
profile: AllProfiles,
#[allow(dead_code)]
os_crypt: Option<OsCrypt>,
@@ -198,16 +193,17 @@ fn load_local_state(browser_dir: &Path) -> Result<LocalState> {
}
fn get_profile_info(local_state: &LocalState) -> Vec<ProfileInfo> {
let mut profile_infos = Vec::new();
for (name, info) in local_state.profile.info_cache.iter() {
profile_infos.push(ProfileInfo {
local_state
.profile
.info_cache
.iter()
.map(|(name, info)| ProfileInfo {
name: info.name.clone(),
folder: name.clone(),
account_name: info.gaia_name.clone(),
account_email: info.user_name.clone(),
});
}
profile_infos
})
.collect()
}
struct EncryptedLogin {
@@ -264,17 +260,16 @@ fn hex_to_bytes(hex: &str) -> Vec<u8> {
decode(hex).unwrap_or_default()
}
fn does_table_exist(conn: &Connection, table_name: &str) -> Result<bool, rusqlite::Error> {
let mut stmt = conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?1")?;
let exists = stmt.exists(params![table_name])?;
Ok(exists)
fn table_exist(conn: &Connection, table_name: &str) -> Result<bool, rusqlite::Error> {
conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?1")?
.exists(params![table_name])
}
fn query_logins(db_path: &str) -> Result<Vec<EncryptedLogin>, rusqlite::Error> {
let conn = Connection::open(db_path)?;
let have_logins = does_table_exist(&conn, "logins")?;
let have_password_notes = does_table_exist(&conn, "password_notes")?;
let have_logins = table_exist(&conn, "logins")?;
let have_password_notes = table_exist(&conn, "password_notes")?;
if !have_logins || !have_password_notes {
return Ok(vec![]);
}
@@ -308,10 +303,7 @@ fn query_logins(db_path: &str) -> Result<Vec<EncryptedLogin>, rusqlite::Error> {
})
})?;
let mut logins = Vec::new();
for login in logins_iter {
logins.push(login?);
}
let logins = logins_iter.collect::<Result<Vec<_>, _>>()?;
Ok(logins)
}

View File

@@ -13,7 +13,7 @@ use crate::util;
//
// TODO: It's possible that there might be multiple possible data directories, depending on the installation method (e.g., snap, flatpak, etc.).
pub const SUPPORTED_BROWSERS: [BrowserConfig; 4] = [
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
BrowserConfig {
name: "Chrome",
data_dir: ".config/google-chrome",
@@ -32,7 +32,7 @@ pub const SUPPORTED_BROWSERS: [BrowserConfig; 4] = [
},
];
pub fn get_crypto_service(
pub(crate) fn get_crypto_service(
browser_name: &String,
_local_state: &LocalState,
) -> Result<Box<dyn CryptoService>> {

View File

@@ -10,7 +10,7 @@ use crate::util;
// Public API
//
pub const SUPPORTED_BROWSERS: [BrowserConfig; 7] = [
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
BrowserConfig {
name: "Chrome",
data_dir: "Library/Application Support/Google/Chrome",
@@ -41,7 +41,7 @@ pub const SUPPORTED_BROWSERS: [BrowserConfig; 7] = [
},
];
pub fn get_crypto_service(
pub(crate) fn get_crypto_service(
browser_name: &String,
_local_state: &LocalState,
) -> Result<Box<dyn CryptoService>> {

View File

@@ -0,0 +1,7 @@
// Platform-specific code
#[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(target_os = "windows", path = "windows.rs")]
#[cfg_attr(target_os = "macos", path = "macos.rs")]
mod native;
pub(crate) use native::*;

View File

@@ -15,8 +15,7 @@ use crate::util;
// Public API
//
// IMPORTANT adjust array size when enabling / disabling chromium importers here
pub const SUPPORTED_BROWSERS: [BrowserConfig; 6] = [
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
BrowserConfig {
name: "Brave",
data_dir: "AppData/Local/BraveSoftware/Brave-Browser/User Data",
@@ -43,7 +42,7 @@ pub const SUPPORTED_BROWSERS: [BrowserConfig; 6] = [
},
];
pub fn get_crypto_service(
pub(crate) fn get_crypto_service(
_browser_name: &str,
local_state: &LocalState,
) -> Result<Box<dyn CryptoService>> {

View File

@@ -0,0 +1,5 @@
#![doc = include_str!("../README.md")]
pub mod chromium;
pub mod metadata;
mod util;

View File

@@ -1,8 +1,7 @@
use std::collections::{HashMap, HashSet};
use crate::{chromium::InstalledBrowserRetriever, PLATFORM_SUPPORTED_BROWSERS};
use crate::chromium::{InstalledBrowserRetriever, PLATFORM_SUPPORTED_BROWSERS};
#[napi(object)]
/// Mechanisms that load data into the importer
pub struct NativeImporterMetadata {
/// Identifies the importer
@@ -24,7 +23,7 @@ pub fn get_supported_importers<T: InstalledBrowserRetriever>(
// Check for installed browsers
let installed_browsers = T::get_installed_browsers().unwrap_or_default();
const IMPORTERS: [(&str, &str); 6] = [
const IMPORTERS: &[(&str, &str)] = &[
("chromecsv", "Chrome"),
("chromiumcsv", "Chromium"),
("bravecsv", "Brave"),
@@ -57,9 +56,7 @@ pub fn get_supported_importers<T: InstalledBrowserRetriever>(
map
}
/*
Tests are cfg-gated based upon OS, and must be compiled/run on each OS for full coverage
*/
// Tests are cfg-gated based upon OS, and must be compiled/run on each OS for full coverage
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -1,9 +1,6 @@
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
use anyhow::{anyhow, Result};
use pbkdf2::{hmac::Hmac, pbkdf2};
use sha1::Sha1;
pub fn split_encrypted_string(encrypted: &[u8]) -> Result<(&str, &[u8])> {
fn split_encrypted_string(encrypted: &[u8]) -> Result<(&str, &[u8])> {
if encrypted.len() < 3 {
return Err(anyhow!(
"Corrupted entry: invalid encrypted string length, expected at least 3 bytes, got {}",
@@ -15,7 +12,14 @@ pub fn split_encrypted_string(encrypted: &[u8]) -> Result<(&str, &[u8])> {
Ok((std::str::from_utf8(version)?, password))
}
pub fn split_encrypted_string_and_validate<'a>(
/// A Chromium password consists of three parts:
/// - Version (3 bytes): "v10", "v11", etc.
/// - Cipher text (chunks of 16 bytes)
/// - Padding (1-15 bytes)
///
/// This function splits the encrypted byte slice into version and cipher text.
/// Padding is included and handled by the underlying cryptographic library.
pub(crate) fn split_encrypted_string_and_validate<'a>(
encrypted: &'a [u8],
supported_versions: &[&str],
) -> Result<(&'a str, &'a [u8])> {
@@ -27,15 +31,22 @@ pub fn split_encrypted_string_and_validate<'a>(
Ok((version, password))
}
pub fn decrypt_aes_128_cbc(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
let decryptor = cbc::Decryptor::<aes::Aes128>::new_from_slices(key, iv)?;
let plaintext: Vec<u8> = decryptor
/// Decrypt using AES-128 in CBC mode.
#[cfg(any(target_os = "linux", target_os = "macos", test))]
pub(crate) fn decrypt_aes_128_cbc(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
cbc::Decryptor::<aes::Aes128>::new_from_slices(key, iv)?
.decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
.map_err(|e| anyhow!("Failed to decrypt: {}", e))?;
Ok(plaintext)
.map_err(|e| anyhow!("Failed to decrypt: {}", e))
}
pub fn derive_saltysalt(password: &[u8], iterations: u32) -> Result<Vec<u8>> {
/// Derives a PBKDF2 key from the static "saltysalt" salt with the given password and iteration count.
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub(crate) fn derive_saltysalt(password: &[u8], iterations: u32) -> Result<Vec<u8>> {
use pbkdf2::{hmac::Hmac, pbkdf2};
use sha1::Sha1;
let mut key = vec![0u8; 16];
pbkdf2::<Hmac<Sha1>>(password, b"saltysalt", iterations, &mut key)
.map_err(|e| anyhow!("Failed to derive master key: {}", e))?;
@@ -44,16 +55,6 @@ pub fn derive_saltysalt(password: &[u8], iterations: u32) -> Result<Vec<u8>> {
#[cfg(test)]
mod tests {
pub fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> {
(0..length).map(|i| offset + i as u8 * increment).collect()
}
pub fn generate_generic_array<N: ArrayLength<u8>>(
offset: u8,
increment: u8,
) -> GenericArray<u8, N> {
GenericArray::generate(|i| offset + i as u8 * increment)
}
use aes::cipher::{
block_padding::Pkcs7,
generic_array::{sequence::GenericSequence, GenericArray},
@@ -64,6 +65,17 @@ mod tests {
const LENGTH10: usize = 10;
const LENGTH0: usize = 0;
fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> {
(0..length).map(|i| offset + i as u8 * increment).collect()
}
fn generate_generic_array<N: ArrayLength<u8>>(
offset: u8,
increment: u8,
) -> GenericArray<u8, N> {
GenericArray::generate(|i| offset + i as u8 * increment)
}
fn run_split_encrypted_string_test<'a, const N: usize>(
successfully_split: bool,
plaintext_to_encrypt: &'a str,

View File

@@ -17,7 +17,7 @@ manual_test = []
anyhow = { workspace = true }
autotype = { path = "../autotype" }
base64 = { workspace = true }
bitwarden_chromium_importer = { path = "../bitwarden_chromium_importer" }
chromium_importer = { path = "../chromium_importer" }
desktop_core = { path = "../core" }
hex = { workspace = true }
napi = { workspace = true, features = ["async"] }

View File

@@ -3,15 +3,6 @@
/* auto-generated by NAPI-RS */
/** Mechanisms that load data into the importer */
export interface NativeImporterMetadata {
/** Identifies the importer */
id: string
/** Describes the strategies used to obtain imported data */
loaders: Array<string>
/** Identifies the instructions for the importer */
instructions: string
}
export declare namespace passwords {
/** The error message returned when a password is not found during retrieval or deletion. */
export const PASSWORD_NOT_FOUND: string
@@ -249,6 +240,11 @@ export declare namespace chromium_importer {
login?: Login
failure?: LoginImportFailure
}
export interface NativeImporterMetadata {
id: string
loaders: Array<string>
instructions: string
}
/** Returns OS aware metadata describing supported Chromium based importers as a JSON string. */
export function getMetadata(): Record<string, NativeImporterMetadata>
export function getInstalledBrowsers(): Array<string>

View File

@@ -1051,6 +1051,10 @@ pub mod logging {
// overriding the default directive for matching targets.
.from_env_lossy();
// With the `tracing-log` feature enabled for the `tracing_subscriber`,
// the registry below will initialize a log compatibility layer, which allows
// the subscriber to consume log::Records as though they were tracing Events.
// https://docs.rs/tracing-subscriber/latest/tracing_subscriber/util/trait.SubscriberInitExt.html#method.init
tracing_subscriber::registry()
.with(filter)
.with(JsLayer)
@@ -1060,11 +1064,13 @@ pub mod logging {
#[napi]
pub mod chromium_importer {
use bitwarden_chromium_importer::chromium::DefaultInstalledBrowserRetriever;
use bitwarden_chromium_importer::chromium::InstalledBrowserRetriever;
use bitwarden_chromium_importer::chromium::LoginImportResult as _LoginImportResult;
use bitwarden_chromium_importer::chromium::ProfileInfo as _ProfileInfo;
use bitwarden_chromium_importer::metadata::NativeImporterMetadata;
use chromium_importer::{
chromium::{
DefaultInstalledBrowserRetriever, InstalledBrowserRetriever,
LoginImportResult as _LoginImportResult, ProfileInfo as _ProfileInfo,
},
metadata::NativeImporterMetadata as _NativeImporterMetadata,
};
use std::collections::HashMap;
#[napi(object)]
@@ -1094,6 +1100,13 @@ pub mod chromium_importer {
pub failure: Option<LoginImportFailure>,
}
#[napi(object)]
pub struct NativeImporterMetadata {
pub id: String,
pub loaders: Vec<&'static str>,
pub instructions: &'static str,
}
impl From<_LoginImportResult> for LoginImportResult {
fn from(l: _LoginImportResult) -> Self {
match l {
@@ -1127,23 +1140,34 @@ pub mod chromium_importer {
}
}
impl From<_NativeImporterMetadata> for NativeImporterMetadata {
fn from(m: _NativeImporterMetadata) -> Self {
NativeImporterMetadata {
id: m.id,
loaders: m.loaders,
instructions: m.instructions,
}
}
}
#[napi]
/// Returns OS aware metadata describing supported Chromium based importers as a JSON string.
pub fn get_metadata() -> HashMap<String, NativeImporterMetadata> {
bitwarden_chromium_importer::metadata::get_supported_importers::<
DefaultInstalledBrowserRetriever,
>()
chromium_importer::metadata::get_supported_importers::<DefaultInstalledBrowserRetriever>()
.into_iter()
.map(|(browser, metadata)| (browser, NativeImporterMetadata::from(metadata)))
.collect()
}
#[napi]
pub fn get_installed_browsers() -> napi::Result<Vec<String>> {
bitwarden_chromium_importer::chromium::DefaultInstalledBrowserRetriever::get_installed_browsers()
chromium_importer::chromium::DefaultInstalledBrowserRetriever::get_installed_browsers()
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
#[napi]
pub fn get_available_profiles(browser: String) -> napi::Result<Vec<ProfileInfo>> {
bitwarden_chromium_importer::chromium::get_available_profiles(&browser)
chromium_importer::chromium::get_available_profiles(&browser)
.map(|profiles| profiles.into_iter().map(ProfileInfo::from).collect())
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
@@ -1153,7 +1177,7 @@ pub mod chromium_importer {
browser: String,
profile_id: String,
) -> napi::Result<Vec<LoginImportResult>> {
bitwarden_chromium_importer::chromium::import_logins(&browser, &profile_id)
chromium_importer::chromium::import_logins(&browser, &profile_id)
.await
.map(|logins| logins.into_iter().map(LoginImportResult::from).collect())
.map_err(|e| napi::Error::from_reason(e.to_string()))

View File

@@ -1,5 +1,5 @@
import { SystemServiceProvider } from "@bitwarden/common/tools/providers";
import type { NativeImporterMetadata } from "@bitwarden/desktop-napi";
import type { chromium_importer } from "@bitwarden/desktop-napi";
import {
ImportType,
DefaultImportMetadataService,
@@ -25,7 +25,9 @@ export class DesktopImportMetadataService
await super.init();
}
private async parseNativeMetaData(raw: Record<string, NativeImporterMetadata>): Promise<void> {
private async parseNativeMetaData(
raw: Record<string, chromium_importer.NativeImporterMetadata>,
): Promise<void> {
const entries = Object.entries(raw).map(([id, meta]) => {
const loaders = meta.loaders.map(this.mapLoader);
const instructions = this.mapInstructions(meta.instructions);

View File

@@ -1,9 +1,9 @@
import { ipcRenderer } from "electron";
import type { NativeImporterMetadata } from "@bitwarden/desktop-napi";
import type { chromium_importer } from "@bitwarden/desktop-napi";
const chromiumImporter = {
getMetadata: (): Promise<Record<string, NativeImporterMetadata>> =>
getMetadata: (): Promise<Record<string, chromium_importer.NativeImporterMetadata>> =>
ipcRenderer.invoke("chromium_importer.getMetadata"),
getInstalledBrowsers: (): Promise<string[]> =>
ipcRenderer.invoke("chromium_importer.getInstalledBrowsers"),

View File

@@ -4181,5 +4181,11 @@
},
"archiveItemConfirmDesc": {
"message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?"
},
"zipPostalCodeLabel": {
"message": "ZIP / Postal code"
},
"cardNumberLabel": {
"message": "Card number"
}
}

View File

@@ -21,6 +21,8 @@ export interface ApproveSshRequestParams {
action: string;
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-approve-ssh-request",
templateUrl: "approve-ssh-request.html",

View File

@@ -10,6 +10,8 @@ import {
CollectionAssignmentResult,
} from "@bitwarden/vault";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
standalone: true,
templateUrl: "./assign-collections-desktop.component.html",

View File

@@ -39,6 +39,8 @@ export const CredentialGeneratorDialogAction = {
type CredentialGeneratorDialogAction = UnionOfValues<typeof CredentialGeneratorDialogAction>;
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "credential-generator-dialog",
templateUrl: "credential-generator-dialog.component.html",

View File

@@ -25,22 +25,46 @@ import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cip
import { ButtonComponent, ButtonModule, DialogService, ToastService } from "@bitwarden/components";
import { ArchiveCipherUtilitiesService, PasswordRepromptService } from "@bitwarden/vault";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-vault-item-footer",
templateUrl: "item-footer.component.html",
imports: [ButtonModule, CommonModule, JslibModule],
})
export class ItemFooterComponent implements OnInit, OnChanges {
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input({ required: true }) cipher: CipherView = new CipherView();
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() collectionId: string | null = null;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input({ required: true }) action: string = "view";
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() masterPasswordAlreadyPrompted: boolean = false;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
@Output() onEdit = new EventEmitter<CipherView>();
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
@Output() onClone = new EventEmitter<CipherView>();
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
@Output() onDelete = new EventEmitter<CipherView>();
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
@Output() onRestore = new EventEmitter<CipherView>();
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
@Output() onCancel = new EventEmitter<CipherView>();
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
@Output() onArchiveToggle = new EventEmitter<CipherView>();
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@ViewChild("submitBtn", { static: false }) submitBtn: ButtonComponent | null = null;
activeUserId: UserId | null = null;

View File

@@ -2,6 +2,8 @@ import { Component } from "@angular/core";
import { CollectionFilterComponent as BaseCollectionFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/collection-filter.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-collection-filter",
templateUrl: "collection-filter.component.html",

View File

@@ -2,6 +2,8 @@ import { Component } from "@angular/core";
import { FolderFilterComponent as BaseFolderFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/folder-filter.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-folder-filter",
templateUrl: "folder-filter.component.html",

View File

@@ -9,6 +9,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-organization-filter",
templateUrl: "organization-filter.component.html",

View File

@@ -2,6 +2,8 @@ import { Component } from "@angular/core";
import { StatusFilterComponent as BaseStatusFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/status-filter.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-status-filter",
templateUrl: "status-filter.component.html",

View File

@@ -5,6 +5,8 @@ import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angul
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
import { CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-type-filter",
templateUrl: "type-filter.component.html",

View File

@@ -2,6 +2,8 @@ import { Component } from "@angular/core";
import { VaultFilterComponent as BaseVaultFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/vault-filter.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-vault-filter",
templateUrl: "vault-filter.component.html",

View File

@@ -21,6 +21,8 @@ import { MenuModule } from "@bitwarden/components";
import { SearchBarService } from "../../../app/layout/search/search-bar.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-vault-items-v2",
templateUrl: "vault-items-v2.component.html",

View File

@@ -94,6 +94,8 @@ import { VaultItemsV2Component } from "./vault-items-v2.component";
const BroadcasterSubscriptionId = "VaultComponent";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-vault",
templateUrl: "vault-v2.component.html",
@@ -138,12 +140,20 @@ const BroadcasterSubscriptionId = "VaultComponent";
export class VaultV2Component<C extends CipherViewLike>
implements OnInit, OnDestroy, CopyClickListener
{
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@ViewChild(VaultItemsV2Component, { static: true })
vaultItemsComponent: VaultItemsV2Component<C> | null = null;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@ViewChild(VaultFilterComponent, { static: true })
vaultFilterComponent: VaultFilterComponent | null = null;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
folderAddEditModalRef: ViewContainerRef | null = null;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@ViewChild(CipherFormComponent)
cipherFormComponent: CipherFormComponent | null = null;

View File

@@ -9,6 +9,12 @@ COPY package*.json ./
COPY . .
RUN npm ci
# Remove commercial packages if LICENSE_TYPE is not 'commercial'
ARG LICENSE_TYPE=oss
RUN if [ "${LICENSE_TYPE}" != "commercial" ] ; then \
rm -rf node_modules/@bitwarden/commercial-sdk-internal ; \
fi
WORKDIR /source/apps/web
ARG NPM_COMMAND=dist:bit:selfhost
RUN npm run ${NPM_COMMAND}

View File

@@ -8,6 +8,7 @@
"proxyIdentity": "http://localhost:33656",
"proxyEvents": "http://localhost:46273",
"proxyNotifications": "http://localhost:61840",
"proxyIcons": "http://localhost:50024",
"wsConnectSrc": "ws://localhost:61840"
},
"additionalRegions": [

View File

@@ -4,6 +4,7 @@
"proxyIdentity": "http://localhost:33657",
"proxyEvents": "http://localhost:46274",
"proxyNotifications": "http://localhost:61841",
"proxyKeyConnector": "http://localhost:5000",
"port": 8081
},
"flags": {}

View File

@@ -11,6 +11,7 @@ import { OrganizationInvite } from "@bitwarden/common/auth/services/organization
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
import { BaseAcceptComponent } from "../../common/base.accept.component";
@@ -35,6 +36,7 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
private organizationInviteService: OrganizationInviteService,
private accountService: AccountService,
private toastService: ToastService,
) {
super(router, platformUtilsService, i18nService, route, authService);
}
@@ -51,14 +53,13 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
return;
}
this.platformUtilService.showToast(
"success",
this.i18nService.t("inviteAccepted"),
invite.initOrganization
this.toastService.showToast({
message: invite.initOrganization
? this.i18nService.t("inviteInitAcceptedDesc")
: this.i18nService.t("inviteAcceptedDesc"),
{ timeout: 10000 },
);
: this.i18nService.t("invitationAcceptedDesc"),
variant: "success",
timeout: 10000,
});
await this.router.navigate(["/vault"]);
}

View File

@@ -451,9 +451,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
"tw-border-solid",
"tw-border-primary-600",
"hover:tw-border-primary-700",
"focus:tw-border-2",
"focus:tw-border-primary-700",
"focus:tw-rounded-lg",
"tw-border-2",
"!tw-border-primary-700",
"tw-rounded-lg",
];
}
case PlanCardState.NotSelected: {

View File

@@ -31,6 +31,7 @@ import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-conso
import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { assertNonNullish } from "@bitwarden/common/auth/utils";
import { PlanSponsorshipType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
@@ -41,7 +42,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { OrganizationId, ProviderId, UserId } from "@bitwarden/common/types/guid";
import { OrgKey } from "@bitwarden/common/types/key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { ToastService } from "@bitwarden/components";
@@ -654,7 +655,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
orgId = this.selfHosted
? await this.createSelfHosted(key, collectionCt, orgKeys)
: await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]);
: await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1], activeUserId);
this.toastService.showToast({
variant: "success",
@@ -808,6 +809,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
collectionCt: string,
orgKeys: [string, EncString],
orgKey: SymmetricCryptoKey,
activeUserId: UserId,
): Promise<string> {
const request = new OrganizationCreateRequest();
request.key = key;
@@ -855,7 +857,14 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
this.formGroup.controls.clientOwnerEmail.value,
request,
);
const providerKey = await this.keyService.getProviderKey(this.providerId);
const providerKey = await firstValueFrom(
this.keyService
.providerKeys$(activeUserId)
.pipe(map((providerKeys) => providerKeys?.[this.providerId as ProviderId] ?? null)),
);
assertNonNullish(providerKey, "Provider key not found");
providerRequest.organizationCreateRequest.key = (
await this.encryptService.wrapSymmetricKey(orgKey, providerKey)
).encryptedString;

View File

@@ -223,7 +223,7 @@
<h2 bitTypography="h2">{{ "manageSubscription" | i18n }}</h2>
<p bitTypography="body1" *ngIf="userOrg.isProviderUser; else isOrganizationOwner">
{{ "manageSubscriptionFromThe" | i18n }}
<a [routerLink]="['/providers', userOrg.providerId, 'manage-client-organizations']">{{
<a [routerLink]="['/providers', userOrg.providerId, 'billing', 'subscription']">{{
"providerPortal" | i18n
}}</a
>.

View File

@@ -70,7 +70,7 @@ type Scenario =
</div>
<div class="tw-col-span-6">
<bit-form-field [disableMargin]="true">
<bit-label>{{ "zipPostalCode" | i18n }}</bit-label>
<bit-label>{{ "zipPostalCodeLabel" | i18n }}</bit-label>
<input
bitInput
type="text"

View File

@@ -83,7 +83,7 @@ type PaymentMethodFormGroup = FormGroup<{
<div class="tw-grid tw-grid-cols-2 tw-gap-4 tw-mb-4">
<div class="tw-col-span-1">
<app-payment-label for="stripe-card-number" required>
{{ "number" | i18n }}
{{ "cardNumberLabel" | i18n }}
</app-payment-label>
<div id="stripe-card-number" class="tw-stripe-form-control"></div>
</div>
@@ -109,7 +109,7 @@ type PaymentMethodFormGroup = FormGroup<{
class="tw-border-none tw-bg-transparent tw-text-primary-600 tw-pr-1"
[position]="'above-end'"
>
<i class="bwi bwi-question-circle tw-text-lg" aria-hidden="true"></i>
<i class="bwi bwi-question-circle tw-text-sm" aria-hidden="true"></i>
</button>
<bit-popover [title]="'cardSecurityCode' | i18n" #cardSecurityCodePopover>
<p class="tw-mb-0">{{ "cardSecurityCodeDescription" | i18n }}</p>
@@ -217,7 +217,7 @@ type PaymentMethodFormGroup = FormGroup<{
</div>
<div class="tw-col-span-6">
<bit-form-field [disableMargin]="true">
<bit-label>{{ "zipPostalCode" | i18n }}</bit-label>
<bit-label>{{ "zipPostalCodeLabel" | i18n }}</bit-label>
<input
bitInput
type="text"

View File

@@ -26,7 +26,7 @@ import { SharedModule } from "../../../shared";
class="tw-absolute tw-bg-background tw-px-1 tw-text-sm tw-text-muted -tw-top-2.5 tw-left-3 tw-mb-0 tw-max-w-full tw-pointer-events-auto"
>
<ng-container *ngTemplateOutlet="defaultContent"></ng-container>
<span class="tw-text-xs tw-font-normal">({{ "required" | i18n }})</span>
<span class="tw-text-[0.625rem] tw-font-normal">({{ "required" | i18n }})</span>
</bit-label>
</div>
`,

View File

@@ -230,6 +230,8 @@ export class StripeService {
'"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
fontSize: "16px",
fontSmoothing: "antialiased",
lineHeight: "1.5",
padding: "8px 12px",
"::placeholder": {
color: null,
},

View File

@@ -1,8 +1,8 @@
<a
class="tw-block tw-h-full tw-max-w-72 tw-overflow-hidden tw-rounded tw-border tw-border-solid tw-border-secondary-300 !tw-text-main tw-transition-all hover:tw-scale-105 hover:tw-no-underline focus:tw-outline-none focus:tw-ring focus:tw-ring-primary-700 focus:tw-ring-offset-2"
class="tw-block tw-h-full tw-max-w-72 tw-rounded-xl !tw-text-main tw-transition-all hover:tw-scale-105 hover:tw-no-underline focus:tw-outline-none focus:tw-ring focus:tw-ring-primary-700 focus:tw-ring-offset-2"
[routerLink]="route"
>
<div class="tw-relative">
<bit-base-card class="tw-relative tw-overflow-hidden tw-h-full">
<div
class="tw-flex tw-h-28 tw-bg-background-alt2 tw-text-center tw-text-primary-300"
[ngClass]="{ 'tw-grayscale': disabled }"
@@ -11,10 +11,10 @@
<bit-icon [icon]="icon" aria-hidden="true"></bit-icon>
</div>
</div>
<div class="tw-p-5" [ngClass]="{ 'tw-grayscale': disabled }">
<bit-card-content [ngClass]="{ 'tw-grayscale': disabled }">
<h3 class="tw-mb-4 tw-text-xl tw-font-bold">{{ title }}</h3>
<p class="tw-mb-0">{{ description }}</p>
</div>
</bit-card-content>
<span
bitBadge
[variant]="requiresPremium ? 'success' : 'primary'"
@@ -24,5 +24,5 @@
<ng-container *ngIf="requiresPremium">{{ "premium" | i18n }}</ng-container>
<ng-container *ngIf="!requiresPremium">{{ "upgrade" | i18n }}</ng-container>
</span>
</div>
</bit-base-card>
</a>

View File

@@ -4,7 +4,12 @@ import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/an
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components";
import {
BadgeModule,
BaseCardComponent,
IconModule,
CardContentComponent,
} from "@bitwarden/components";
import { PreloadedEnglishI18nModule } from "../../../../core/tests";
import { ReportVariant } from "../models/report-variant";
@@ -16,7 +21,15 @@ export default {
component: ReportCardComponent,
decorators: [
moduleMetadata({
imports: [JslibModule, BadgeModule, IconModule, RouterTestingModule, PremiumBadgeComponent],
imports: [
JslibModule,
BadgeModule,
CardContentComponent,
IconModule,
RouterTestingModule,
PremiumBadgeComponent,
BaseCardComponent,
],
}),
applicationConfig({
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],

View File

@@ -4,7 +4,12 @@ import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/an
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components";
import {
BadgeModule,
BaseCardComponent,
CardContentComponent,
IconModule,
} from "@bitwarden/components";
import { PreloadedEnglishI18nModule } from "../../../../core/tests";
import { reports } from "../../reports";
@@ -18,7 +23,15 @@ export default {
component: ReportListComponent,
decorators: [
moduleMetadata({
imports: [JslibModule, BadgeModule, RouterTestingModule, IconModule, PremiumBadgeComponent],
imports: [
JslibModule,
BadgeModule,
RouterTestingModule,
IconModule,
PremiumBadgeComponent,
CardContentComponent,
BaseCardComponent,
],
declarations: [ReportCardComponent],
}),
applicationConfig({

View File

@@ -1,13 +1,15 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { BaseCardComponent, CardContentComponent } from "@bitwarden/components";
import { SharedModule } from "../../../shared/shared.module";
import { ReportCardComponent } from "./report-card/report-card.component";
import { ReportListComponent } from "./report-list/report-list.component";
@NgModule({
imports: [CommonModule, SharedModule],
imports: [CommonModule, SharedModule, BaseCardComponent, CardContentComponent],
declarations: [ReportCardComponent, ReportListComponent],
exports: [ReportCardComponent, ReportListComponent],
})

View File

@@ -22,7 +22,6 @@
[route]="['../', org.id]"
(mainContentClicked)="toggle()"
[routerLinkActiveOptions]="{ exact: true }"
(click)="showInactiveSubscriptionDialog(org)"
>
<i
slot="end"

View File

@@ -3,7 +3,7 @@
import { CommonModule } from "@angular/common";
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { combineLatest, firstValueFrom, map, Observable, switchMap } from "rxjs";
import { combineLatest, map, Observable, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@@ -12,7 +12,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { DialogService, NavigationModule } from "@bitwarden/components";
import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module";
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@@ -76,7 +75,6 @@ export class OrgSwitcherComponent {
private organizationService: OrganizationService,
protected billingApiService: BillingApiServiceAbstraction,
private accountService: AccountService,
private organizationWarningsService: OrganizationWarningsService,
) {}
protected toggle(event?: MouseEvent) {
@@ -84,9 +82,4 @@ export class OrgSwitcherComponent {
this.open = !this.open;
this.openChange.emit(this.open);
}
showInactiveSubscriptionDialog = async (organization: Organization) =>
await firstValueFrom(
this.organizationWarningsService.showInactiveSubscriptionDialog$(organization),
);
}

Some files were not shown because too many files have changed in this diff Show More