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:
25
.claude/prompts/review-code.md
Normal file
25
.claude/prompts/review-code.md
Normal 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
2
.github/CODEOWNERS
vendored
@@ -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
|
||||
|
||||
1
.github/renovate.json5
vendored
1
.github/renovate.json5
vendored
@@ -139,6 +139,7 @@
|
||||
"@babel/core",
|
||||
"@babel/preset-env",
|
||||
"@bitwarden/sdk-internal",
|
||||
"@bitwarden/commercial-sdk-internal",
|
||||
"@electron/fuses",
|
||||
"@electron/notarize",
|
||||
"@electron/rebuild",
|
||||
|
||||
14
.github/workflows/build-browser.yml
vendored
14
.github/workflows/build-browser.yml
vendored
@@ -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
|
||||
|
||||
18
.github/workflows/build-cli.yml
vendored
18
.github/workflows/build-cli.yml
vendored
@@ -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
|
||||
|
||||
10
.github/workflows/build-web.yml
vendored
10
.github/workflows/build-web.yml
vendored
@@ -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
|
||||
|
||||
3
.github/workflows/lint.yml
vendored
3
.github/workflows/lint.yml
vendored
@@ -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
|
||||
|
||||
|
||||
118
.github/workflows/review-code.yml
vendored
118
.github/workflows/review-code.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -10,7 +10,6 @@ Thumbs.db
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
.claude
|
||||
.serena
|
||||
|
||||
# Visual Studio Code
|
||||
|
||||
2
.npmrc
2
.npmrc
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(),
|
||||
}));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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 = "";
|
||||
}
|
||||
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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))),
|
||||
);
|
||||
|
||||
|
||||
@@ -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()],
|
||||
|
||||
@@ -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",
|
||||
|
||||
54
apps/desktop/desktop_native/Cargo.lock
generated
54
apps/desktop/desktop_native/Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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>> {
|
||||
@@ -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>> {
|
||||
@@ -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::*;
|
||||
@@ -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>> {
|
||||
5
apps/desktop/desktop_native/chromium_importer/src/lib.rs
Normal file
5
apps/desktop/desktop_native/chromium_importer/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod chromium;
|
||||
pub mod metadata;
|
||||
mod util;
|
||||
@@ -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::*;
|
||||
@@ -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,
|
||||
@@ -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"] }
|
||||
|
||||
14
apps/desktop/desktop_native/napi/index.d.ts
vendored
14
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -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>
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"proxyIdentity": "http://localhost:33657",
|
||||
"proxyEvents": "http://localhost:46274",
|
||||
"proxyNotifications": "http://localhost:61841",
|
||||
"proxyKeyConnector": "http://localhost:5000",
|
||||
"port": 8081
|
||||
},
|
||||
"flags": {}
|
||||
|
||||
@@ -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"]);
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
>.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
`,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)],
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
[route]="['../', org.id]"
|
||||
(mainContentClicked)="toggle()"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
(click)="showInactiveSubscriptionDialog(org)"
|
||||
>
|
||||
<i
|
||||
slot="end"
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user