1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-07 04:03:29 +00:00

Merge branch 'main' into pm-18701-optional-payment-modal-after-signup

This commit is contained in:
cyprain-okeke
2025-06-18 12:40:04 +01:00
committed by GitHub
69 changed files with 611 additions and 263 deletions

1
.github/CODEOWNERS vendored
View File

@@ -140,6 +140,7 @@ libs/components @bitwarden/team-ui-foundation
libs/ui @bitwarden/team-ui-foundation
apps/browser/src/platform/popup/layout @bitwarden/team-ui-foundation
apps/browser/src/popup/app-routing.animations.ts @bitwarden/team-ui-foundation
apps/browser/src/popup/components/extension-anon-layout-wrapper @bitwarden/team-ui-foundation
apps/web/src/app/layouts @bitwarden/team-ui-foundation

View File

@@ -36,8 +36,7 @@ on:
description: "New version override (leave blank for automatic calculation, example: '2024.1.0')"
required: false
type: string
permissions: {}
jobs:
setup:
name: Setup
@@ -57,51 +56,11 @@ jobs:
fi
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
cut_branch:
name: Cut branch
if: ${{ needs.setup.outputs.branch == 'rc' }}
needs: setup
runs-on: ubuntu-24.04
steps:
- name: Generate GH App token
uses: actions/create-github-app-token@30bf6253fa41bdc8d1501d202ad15287582246b4 # v2.0.3
id: app-token
with:
app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Check out target ref
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ inputs.target_ref }}
token: ${{ steps.app-token.outputs.token }}
- name: Check if ${{ needs.setup.outputs.branch }} branch exists
env:
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then
echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Cut branch
env:
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
git switch --quiet --create $BRANCH_NAME
git push --quiet --set-upstream origin $BRANCH_NAME
bump_version:
name: Bump Version
if: ${{ always() }}
runs-on: ubuntu-24.04
needs:
- cut_branch
- setup
needs: setup
outputs:
version_browser: ${{ steps.set-final-version-output.outputs.version_browser }}
version_cli: ${{ steps.set-final-version-output.outputs.version_cli }}
@@ -441,15 +400,13 @@ jobs:
- name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git push
cherry_pick:
name: Cherry-Pick Commit(s)
cut_branch:
name: Cut branch
if: ${{ needs.setup.outputs.branch == 'rc' }}
runs-on: ubuntu-24.04
needs:
- bump_version
- setup
- bump_version
runs-on: ubuntu-24.04
steps:
- name: Generate GH App token
uses: actions/create-github-app-token@30bf6253fa41bdc8d1501d202ad15287582246b4 # v2.0.3
@@ -458,43 +415,24 @@ jobs:
app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Check out main branch
- name: Check out target ref
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: main
ref: ${{ inputs.target_ref }}
token: ${{ steps.app-token.outputs.token }}
- name: Configure Git
- name: Check if ${{ needs.setup.outputs.branch }} branch exists
env:
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
git config --local user.email "actions@github.com"
git config --local user.name "Github Actions"
if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then
echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Perform cherry-pick(s)
- name: Cut branch
env:
BRANCH_NAME: ${{ needs.setup.outputs.branch }}
run: |
# Function for cherry-picking
cherry_pick () {
local package_path="apps/$1/package.json"
local source_branch=$2
local destination_branch=$3
# Get project commit/version from source branch
git switch $source_branch
SOURCE_COMMIT=$(git log --reverse --pretty=format:"%H" --max-count=1 $package_path)
SOURCE_VERSION=$(cat $package_path | jq -r '.version')
# Get project commit/version from destination branch
git switch $destination_branch
DESTINATION_VERSION=$(cat $package_path | jq -r '.version')
if [[ "$DESTINATION_VERSION" != "$SOURCE_VERSION" ]]; then
git cherry-pick --strategy-option=theirs -x $SOURCE_COMMIT
git push -u origin $destination_branch
fi
}
# Cherry-pick from 'main' into 'rc'
cherry_pick browser main rc
cherry_pick cli main rc
cherry_pick desktop main rc
cherry_pick web main rc
git switch --quiet --create $BRANCH_NAME
git push --quiet --set-upstream origin $BRANCH_NAME

View File

@@ -5062,6 +5062,9 @@
"unlockPinSet": {
"message": "Unlock PIN set"
},
"unlockBiometricSet": {
"message": "Unlock biometrics set"
},
"authenticating": {
"message": "Authenticating"
},

View File

@@ -16,7 +16,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { BrowserPlatformUtilsService } from "../../../platform/services/platform-utils/browser-platform-utils.service";
import { ExtensionAnonLayoutWrapperDataService } from "../extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service";
import { ExtensionAnonLayoutWrapperDataService } from "../../../popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service";
import { ExtensionLoginComponentService } from "./extension-login-component.service";

View File

@@ -11,7 +11,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { ExtensionAnonLayoutWrapperDataService } from "../extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service";
import { ExtensionAnonLayoutWrapperDataService } from "../../../popup/components/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service";
@Injectable()
export class ExtensionLoginComponentService

View File

@@ -534,6 +534,11 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
if (!successful) {
await this.biometricStateService.setFingerprintValidated(false);
}
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("unlockBiometricSet"),
});
} catch (error) {
this.form.controls.biometric.setValue(false);
this.validationService.showError(error);

View File

@@ -88,6 +88,7 @@ export class WebauthnUtils {
getClientExtensionResults: () => ({
credProps: result.extensions.credProps,
}),
toJSON: () => Fido2Utils.createResultToJson(result),
} as PublicKeyCredential;
// Modify prototype chains to fix `instanceof` calls.
@@ -134,6 +135,7 @@ export class WebauthnUtils {
} as AuthenticatorAssertionResponse,
getClientExtensionResults: () => ({}),
authenticatorAttachment: "platform",
toJSON: () => Fido2Utils.getResultToJson(result),
} as PublicKeyCredential;
// Modify prototype chains to fix `instanceof` calls.

View File

@@ -105,9 +105,11 @@ export class PopupRouterCacheService {
* Navigate back in history
*/
async back() {
await this.state.update((prevState) => (prevState ? prevState.slice(0, -1) : []));
const history = await this.state.update((prevState) =>
prevState ? prevState.slice(0, -1) : [],
);
if (this.hasNavigated) {
if (this.hasNavigated && history.length) {
this.location.back();
return;
}

View File

@@ -16,10 +16,7 @@ import {
unauthGuardFn,
} from "@bitwarden/angular/auth/guards";
import {
AnonLayoutWrapperComponent,
AnonLayoutWrapperData,
DevicesIcon,
LockIcon,
LoginComponent,
LoginDecryptionOptionsComponent,
LoginSecondaryContentComponent,
@@ -41,13 +38,10 @@ import {
UserLockIcon,
VaultIcon,
} from "@bitwarden/auth/angular";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, Icons } from "@bitwarden/components";
import { LockComponent } from "@bitwarden/key-management-ui";
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
import {
ExtensionAnonLayoutWrapperComponent,
ExtensionAnonLayoutWrapperData,
} from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component";
import { fido2AuthGuard } from "../auth/popup/guards/fido2-auth.guard";
import { SetPasswordComponent } from "../auth/popup/set-password.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
@@ -89,6 +83,10 @@ import { TrashComponent } from "../vault/popup/settings/trash.component";
import { VaultSettingsV2Component } from "../vault/popup/settings/vault-settings-v2.component";
import { RouteElevation } from "./app-routing.animations";
import {
ExtensionAnonLayoutWrapperComponent,
ExtensionAnonLayoutWrapperData,
} from "./components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component";
import { debounceNavigationGuard } from "./services/debounce-navigation.service";
import { TabsV2Component } from "./tabs-v2.component";
@@ -504,7 +502,7 @@ const routes: Routes = [
path: "lock",
canActivate: [lockGuard()],
data: {
pageIcon: LockIcon,
pageIcon: Icons.LockIcon,
pageTitle: {
key: "yourVaultIsLockedV2",
},

View File

@@ -26,7 +26,6 @@ import {
import { AccountComponent } from "../auth/popup/account-switching/account.component";
import { CurrentAccountComponent } from "../auth/popup/account-switching/current-account.component";
import { ExtensionAnonLayoutWrapperComponent } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component";
import { SetPasswordComponent } from "../auth/popup/set-password.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component";
@@ -44,6 +43,7 @@ import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popou
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { ExtensionAnonLayoutWrapperComponent } from "./components/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component";
import { UserVerificationComponent } from "./components/user-verification.component";
import { ServicesModule } from "./services/services.module";
import { TabsV2Component } from "./tabs-v2.component";

View File

@@ -3,7 +3,7 @@ import { Observable, Subject } from "rxjs";
import {
AnonLayoutWrapperDataService,
DefaultAnonLayoutWrapperDataService,
} from "@bitwarden/auth/angular";
} from "@bitwarden/components";
import { ExtensionAnonLayoutWrapperData } from "./extension-anon-layout-wrapper.component";

View File

@@ -5,22 +5,23 @@ import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router";
import { Subject, filter, switchMap, takeUntil, tap } from "rxjs";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import {
Icon,
Icons,
IconModule,
Translation,
AnonLayoutComponent,
AnonLayoutWrapperData,
AnonLayoutWrapperDataService,
} from "@bitwarden/auth/angular";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Icon, IconModule, Translation } from "@bitwarden/components";
} from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component";
import { AccountSwitcherService } from "../../../auth/popup/account-switching/services/account-switcher.service";
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
import { CurrentAccountComponent } from "../account-switching/current-account.component";
import { AccountSwitcherService } from "../account-switching/services/account-switcher.service";
import { ExtensionBitwardenLogo } from "./extension-bitwarden-logo.icon";
export interface ExtensionAnonLayoutWrapperData extends AnonLayoutWrapperData {
showAcctSwitcher?: boolean;
@@ -61,7 +62,7 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
protected hideFooter: boolean;
protected theme: string;
protected logo = ExtensionBitwardenLogo;
protected logo = Icons.ExtensionBitwardenLogo;
constructor(
private router: Router,

View File

@@ -9,7 +9,6 @@ import {
} from "@storybook/angular";
import { of } from "rxjs";
import { AnonLayoutWrapperDataService, LockIcon } from "@bitwarden/auth/angular";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
@@ -23,13 +22,15 @@ import {
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { UserId } from "@bitwarden/common/types/guid";
import { ButtonModule, I18nMockService } from "@bitwarden/components";
import {
AnonLayoutWrapperDataService,
ButtonModule,
Icons,
I18nMockService,
} from "@bitwarden/components";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { RegistrationCheckEmailIcon } from "../../../../../../libs/auth/src/angular/icons/registration-check-email.icon";
import { AccountSwitcherService } from "../../../auth/popup/account-switching/services/account-switcher.service";
import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service";
import { AccountSwitcherService } from "../account-switching/services/account-switcher.service";
import { ExtensionAnonLayoutWrapperDataService } from "./extension-anon-layout-wrapper-data.service";
import {
@@ -38,7 +39,7 @@ import {
} from "./extension-anon-layout-wrapper.component";
export default {
title: "Auth/Extension Anon Layout Wrapper",
title: "Browser/Extension Anon Layout Wrapper",
component: ExtensionAnonLayoutWrapperComponent,
} as Meta;
@@ -142,6 +143,8 @@ const decorators = (options: {
switchAccounts: "Switch accounts",
back: "Back",
activeAccount: "Active account",
appLogoLabel: "app logo label",
bitwardenAccount: "Bitwarden Account",
});
},
},
@@ -241,7 +244,7 @@ const initialData: ExtensionAnonLayoutWrapperData = {
pageSubtitle: {
key: "finishCreatingYourAccountBySettingAPassword",
},
pageIcon: LockIcon,
pageIcon: Icons.LockIcon,
showAcctSwitcher: true,
showBackButton: true,
showLogo: true,
@@ -255,7 +258,7 @@ const changedData: ExtensionAnonLayoutWrapperData = {
pageSubtitle: {
key: "checkYourEmail",
},
pageIcon: RegistrationCheckEmailIcon,
pageIcon: Icons.RegistrationCheckEmailIcon,
showAcctSwitcher: false,
showBackButton: false,
showLogo: false,

View File

@@ -22,7 +22,6 @@ import {
} from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import {
AnonLayoutWrapperDataService,
LoginComponentService,
TwoFactorAuthComponentService,
TwoFactorAuthEmailComponentService,
@@ -121,7 +120,12 @@ import {
} from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service";
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
import { CompactModeService, DialogService, ToastService } from "@bitwarden/components";
import {
AnonLayoutWrapperDataService,
CompactModeService,
DialogService,
ToastService,
} from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import {
BiometricsService,
@@ -138,7 +142,6 @@ import {
import { AccountSwitcherService } from "../../auth/popup/account-switching/services/account-switcher.service";
import { ForegroundLockService } from "../../auth/popup/accounts/foreground-lock.service";
import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service";
import { ExtensionLoginComponentService } from "../../auth/popup/login/extension-login-component.service";
import { ExtensionSsoComponentService } from "../../auth/popup/login/extension-sso-component.service";
import { ExtensionLogoutService } from "../../auth/popup/logout/extension-logout.service";
@@ -181,6 +184,7 @@ import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-u
import { Fido2UserVerificationService } from "../../vault/services/fido2-user-verification.service";
import { VaultBrowserStateService } from "../../vault/services/vault-browser-state.service";
import { VaultFilterService } from "../../vault/services/vault-filter.service";
import { ExtensionAnonLayoutWrapperDataService } from "../components/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service";
import { DebounceNavigationService } from "./debounce-navigation.service";
import { InitService } from "./init.service";

View File

@@ -16,11 +16,8 @@ import {
} from "@bitwarden/angular/auth/guards";
import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route";
import {
AnonLayoutWrapperComponent,
AnonLayoutWrapperData,
LoginComponent,
LoginSecondaryContentComponent,
LockIcon,
LoginViaAuthRequestComponent,
PasswordHintComponent,
RegistrationFinishComponent,
@@ -42,6 +39,7 @@ import {
DeviceVerificationIcon,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, Icons } from "@bitwarden/components";
import { LockComponent } from "@bitwarden/key-management-ui";
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
@@ -292,7 +290,7 @@ const routes: Routes = [
path: "lock",
canActivate: [lockGuard()],
data: {
pageIcon: LockIcon,
pageIcon: Icons.LockIcon,
pageTitle: {
key: "yourVaultIsLockedV2",
},

View File

@@ -9,6 +9,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
import { CalloutModule, DialogModule } from "@bitwarden/components";
import { AssignCollectionsComponent } from "@bitwarden/vault";
import { DeleteAccountComponent } from "../auth/delete-account.component";
import { LoginModule } from "../auth/login/login.module";
@@ -55,6 +56,7 @@ import { SharedModule } from "./shared/shared.module";
DeleteAccountComponent,
UserVerificationComponent,
NavComponent,
AssignCollectionsComponent,
VaultV2Component,
],
declarations: [

View File

@@ -3812,5 +3812,139 @@
"message": "Learn more about SSH agent",
"description": "Two part message",
"example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent"
},
"assignToCollections": {
"message": "Assign to collections"
},
"assignToTheseCollections": {
"message": "Assign to these collections"
},
"bulkCollectionAssignmentDialogDescriptionSingular": {
"message": "Only organization members with access to these collections will be able to see the item."
},
"bulkCollectionAssignmentDialogDescriptionPlural": {
"message": "Only organization members with access to these collections will be able to see the items."
},
"noCollectionsAssigned": {
"message": "No collections have been assigned"
},
"assign": {
"message": "Assign"
},
"bulkCollectionAssignmentDialogDescription": {
"message": "Only organization members with access to these collections will be able to see the items."
},
"bulkCollectionAssignmentWarning": {
"message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.",
"placeholders": {
"total_count": {
"content": "$1",
"example": "10"
},
"readonly_count": {
"content": "$2"
}
}
},
"selectCollectionsToAssign": {
"message": "Select collections to assign"
},
"personalItemsTransferWarning": {
"message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.",
"placeholders": {
"personal_items_count": {
"content": "$1",
"example": "2 items"
}
}
},
"personalItemsWithOrgTransferWarning": {
"message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.",
"placeholders": {
"personal_items_count": {
"content": "$1",
"example": "2 items"
},
"org": {
"content": "$2",
"example": "Organization name"
}
}
},
"personalItemTransferWarningSingular": {
"message": "1 item will be permanently transferred to the selected organization. You will no longer own this item."
},
"personalItemWithOrgTransferWarningSingular": {
"message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.",
"placeholders": {
"org": {
"content": "$1",
"example": "Organization name"
}
}
},
"successfullyAssignedCollections": {
"message": "Successfully assigned collections"
},
"nothingSelected": {
"message": "You have not selected anything."
},
"itemsMovedToOrg": {
"message": "Items moved to $ORGNAME$",
"placeholders": {
"orgname": {
"content": "$1",
"example": "Company Name"
}
}
},
"itemMovedToOrg": {
"message": "Item moved to $ORGNAME$",
"placeholders": {
"orgname": {
"content": "$1",
"example": "Company Name"
}
}
},
"movedItemsToOrg": {
"message": "Selected items moved to $ORGNAME$",
"placeholders": {
"orgname": {
"content": "$1",
"example": "Company Name"
}
},
"personalItemsTransferWarningPlural": {
"message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.",
"placeholders": {
"personal_items_count": {
"content": "$1",
"example": "2"
}
}
},
"personalItemWithOrgTransferWarningSingular": {
"message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.",
"placeholders": {
"org": {
"content": "$1",
"example": "Organization name"
}
}
},
"personalItemsWithOrgTransferWarningPlural": {
"message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.",
"placeholders": {
"personal_items_count": {
"content": "$1",
"example": "2"
},
"org": {
"content": "$2",
"example": "Organization name"
}
}
}
}
}

View File

@@ -0,0 +1,33 @@
<bit-dialog dialogSize="large">
<span bitDialogTitle>
{{ "assignToCollections" | i18n }}
<span class="tw-text-sm tw-normal-case tw-text-muted">
{{ editableItemCount | pluralize: ("item" | i18n) : ("items" | i18n) }}
</span>
</span>
<div bitDialogContent>
<assign-collections
[params]="params"
[submitBtn]="assignSubmitButton"
(onCollectionAssign)="onCollectionAssign($event)"
(editableItemCountChange)="editableItemCount = $event"
></assign-collections>
</div>
<ng-container bitDialogFooter>
<button
#assignSubmitButton
form="assign_collections_form"
type="submit"
bitButton
bitFormButton
buttonType="primary"
>
{{ "assign" | i18n }}
</button>
<button type="button" bitButton buttonType="secondary" bitDialogClose>
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@@ -0,0 +1,36 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { PluralizePipe } from "@bitwarden/angular/pipes/pluralize.pipe";
import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components";
import {
AssignCollectionsComponent,
CollectionAssignmentParams,
CollectionAssignmentResult,
} from "@bitwarden/vault";
@Component({
standalone: true,
templateUrl: "./assign-collections-desktop.component.html",
imports: [AssignCollectionsComponent, PluralizePipe, DialogModule, ButtonModule, JslibModule],
})
export class AssignCollectionsDesktopComponent {
protected editableItemCount: number;
constructor(
@Inject(DIALOG_DATA) public params: CollectionAssignmentParams,
private dialogRef: DialogRef<CollectionAssignmentResult>,
) {}
protected async onCollectionAssign(result: CollectionAssignmentResult) {
this.dialogRef.close(result);
}
static open(dialogService: DialogService, config: DialogConfig<CollectionAssignmentParams>) {
return dialogService.open<CollectionAssignmentResult, CollectionAssignmentParams>(
AssignCollectionsDesktopComponent,
config,
);
}
}

View File

@@ -0,0 +1 @@
export * from "./assign-collections-desktop.component";

View File

@@ -9,7 +9,7 @@ import {
ViewContainerRef,
} from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs";
import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom, Observable } from "rxjs";
import { filter, map, take } from "rxjs/operators";
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
@@ -19,6 +19,8 @@ import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/vie
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
@@ -57,6 +59,7 @@ import {
CipherFormMode,
CipherFormModule,
CipherViewComponent,
CollectionAssignmentResult,
DecryptionFailureDialogComponent,
DefaultChangeLoginPasswordService,
DefaultCipherFormConfigService,
@@ -69,6 +72,7 @@ import { DesktopCredentialGenerationService } from "../../../services/desktop-ci
import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service";
import { invokeMenu, RendererMenuItem } from "../../../utils";
import { AssignCollectionsDesktopComponent } from "./assign-collections";
import { ItemFooterComponent } from "./item-footer.component";
import { VaultFilterComponent } from "./vault-filter/vault-filter.component";
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
@@ -142,6 +146,11 @@ export class VaultV2Component implements OnInit, OnDestroy {
config: CipherFormConfig | null = null;
isSubmitting = false;
private organizations$: Observable<Organization[]> = this.accountService.activeAccount$.pipe(
map((a) => a?.id),
switchMap((id) => this.organizationService.organizations$(id)),
);
protected canAccessAttachments$ = this.accountService.activeAccount$.pipe(
filter((account): account is Account => !!account),
switchMap((account) =>
@@ -151,6 +160,8 @@ export class VaultV2Component implements OnInit, OnDestroy {
private modal: ModalRef | null = null;
private componentIsDestroyed$ = new Subject<boolean>();
private allOrganizations: Organization[] = [];
private allCollections: CollectionView[] = [];
constructor(
private route: ActivatedRoute,
@@ -176,6 +187,7 @@ export class VaultV2Component implements OnInit, OnDestroy {
private formConfigService: CipherFormConfigService,
private premiumUpgradePromptService: PremiumUpgradePromptService,
private collectionService: CollectionService,
private organizationService: OrganizationService,
private folderService: FolderService,
) {}
@@ -312,6 +324,16 @@ export class VaultV2Component implements OnInit, OnDestroy {
});
});
}
this.organizations$.pipe(takeUntil(this.componentIsDestroyed$)).subscribe((orgs) => {
this.allOrganizations = orgs;
});
this.collectionService.decryptedCollections$
.pipe(takeUntil(this.componentIsDestroyed$))
.subscribe((collections) => {
this.allCollections = collections;
});
}
ngOnDestroy() {
@@ -364,7 +386,14 @@ export class VaultV2Component implements OnInit, OnDestroy {
cipher.collectionIds.includes(c.id),
) ?? null;
this.action = "view";
await this.go().catch(() => {});
await this.eventCollectionService.collect(
EventType.Cipher_ClientViewed,
cipher.id,
false,
cipher.organizationId,
);
}
async openAttachmentsDialog() {
@@ -420,6 +449,16 @@ export class VaultV2Component implements OnInit, OnDestroy {
},
});
}
if (cipher.canAssignToCollections) {
menu.push({
label: this.i18nService.t("assignToCollections"),
click: () =>
this.functionWithChangeDetection(async () => {
await this.shareCipher(cipher);
}),
});
}
}
switch (cipher.type) {
@@ -531,6 +570,36 @@ export class VaultV2Component implements OnInit, OnDestroy {
await this.go().catch(() => {});
}
async shareCipher(cipher: CipherView) {
if (!cipher) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("nothingSelected"),
});
return;
}
if (!(await this.passwordReprompt(cipher))) {
return;
}
const availableCollections = this.getAvailableCollections(cipher);
const dialog = AssignCollectionsDesktopComponent.open(this.dialogService, {
data: {
ciphers: [cipher],
organizationId: cipher.organizationId as OrganizationId,
availableCollections,
},
});
const result = await lastValueFrom(dialog.closed);
if (result === CollectionAssignmentResult.Saved) {
await this.savedCipher(cipher);
}
}
async addCipher(type: CipherType) {
if (this.action === "add") {
return;
@@ -603,6 +672,16 @@ export class VaultV2Component implements OnInit, OnDestroy {
await this.go().catch(() => {});
}
private getAvailableCollections(cipher: CipherView): CollectionView[] {
const orgId = cipher.organizationId;
if (!orgId || orgId === "MyVault") {
return [];
}
const organization = this.allOrganizations.find((o) => o.id === orgId);
return this.allCollections.filter((c) => c.organizationId === organization?.id && !c.readOnly);
}
private calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string {
if (vaultFilter.status === "favorites") {
return "searchFavorites";

View File

@@ -17,7 +17,6 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { deepLinkGuard } from "../../auth/guards/deep-link/deep-link.guard";
import { VaultModule } from "./collections/vault.module";
import { isEnterpriseOrgGuard } from "./guards/is-enterprise-org.guard";
import { organizationPermissionsGuard } from "./guards/org-permissions.guard";
import { organizationRedirectGuard } from "./guards/org-redirect.guard";
import { AdminConsoleIntegrationsComponent } from "./integrations/integrations.component";
@@ -42,10 +41,7 @@ const routes: Routes = [
},
{
path: "integrations",
canActivate: [
isEnterpriseOrgGuard(false),
organizationPermissionsGuard(canAccessIntegrations),
],
canActivate: [organizationPermissionsGuard(canAccessIntegrations)],
component: AdminConsoleIntegrationsComponent,
data: {
titleId: "integrations",

View File

@@ -3,11 +3,10 @@
import { Component, inject } from "@angular/core";
import { Params } from "@angular/router";
import { BitwardenLogo } from "@bitwarden/auth/angular";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { OrganizationSponsorshipResponse } from "@bitwarden/common/admin-console/models/response/organization-sponsorship.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { ToastService } from "@bitwarden/components";
import { Icons, ToastService } from "@bitwarden/components";
import { BaseAcceptComponent } from "../../../common/base.accept.component";
@@ -22,7 +21,7 @@ import { BaseAcceptComponent } from "../../../common/base.accept.component";
standalone: false,
})
export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent {
protected logo = BitwardenLogo;
protected logo = Icons.BitwardenLogo;
failedShortMessage = "inviteAcceptFailedShort";
failedMessage = "inviteAcceptFailed";

View File

@@ -11,8 +11,6 @@ import {
activeAuthGuard,
} from "@bitwarden/angular/auth/guards";
import {
AnonLayoutWrapperComponent,
AnonLayoutWrapperData,
PasswordHintComponent,
RegistrationFinishComponent,
RegistrationStartComponent,
@@ -22,7 +20,6 @@ import {
RegistrationLinkExpiredComponent,
LoginComponent,
LoginSecondaryContentComponent,
LockIcon,
TwoFactorTimeoutIcon,
UserLockIcon,
SsoKeyIcon,
@@ -39,6 +36,7 @@ import {
NewDeviceVerificationComponent,
DeviceVerificationIcon,
} from "@bitwarden/auth/angular";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, Icons } from "@bitwarden/components";
import { LockComponent } from "@bitwarden/key-management-ui";
import { VaultIcons } from "@bitwarden/vault";
@@ -399,7 +397,7 @@ const routes: Routes = [
pageTitle: {
key: "yourVaultIsLockedV2",
},
pageIcon: LockIcon,
pageIcon: Icons.LockIcon,
showReadonlyHostname: true,
} satisfies AnonLayoutWrapperData,
},

View File

@@ -4,7 +4,6 @@ import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -17,7 +16,7 @@ import { SendAccessResponse } from "@bitwarden/common/tools/send/models/response
import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view";
import { SEND_KDF_ITERATIONS } from "@bitwarden/common/tools/send/send-kdf";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { NoItemsModule, ToastService } from "@bitwarden/components";
import { AnonLayoutWrapperDataService, NoItemsModule, ToastService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import { ExpiredSendIcon } from "@bitwarden/send-ui";

View File

@@ -1,9 +1,9 @@
import { TestBed } from "@angular/core/testing";
import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum";
import { AnonLayoutWrapperDataService } from "@bitwarden/components";
import {
BrowserExtensionPromptService,

View File

@@ -2,11 +2,11 @@ import { DestroyRef, Injectable } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { BehaviorSubject, fromEvent } from "rxjs";
import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum";
import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values";
import { AnonLayoutWrapperDataService } from "@bitwarden/components";
export const BrowserPromptState = {
Loading: "loading",

View File

@@ -3,12 +3,12 @@
import { Component } from "@angular/core";
import { ActivatedRoute, Params, Router } from "@angular/router";
import { BitwardenLogo } from "@bitwarden/auth/angular";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { ProviderUserAcceptRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-accept.request";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Icons } from "@bitwarden/components";
import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept.component";
@Component({
@@ -17,7 +17,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept
standalone: false,
})
export class AcceptProviderComponent extends BaseAcceptComponent {
protected logo = BitwardenLogo;
protected logo = Icons.BitwardenLogo;
providerName: string;
providerId: string;
providerUserId: string;

View File

@@ -2,8 +2,8 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { authGuard } from "@bitwarden/angular/auth/guards";
import { AnonLayoutWrapperComponent } from "@bitwarden/auth/angular";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { AnonLayoutWrapperComponent } from "@bitwarden/components";
import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component";
import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component";

View File

@@ -1,7 +1,7 @@
import { Component } from "@angular/core";
import { Params } from "@angular/router";
import { BitwardenLogo } from "@bitwarden/auth/angular";
import { Icons } from "@bitwarden/components";
import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept.component";
@Component({
@@ -10,7 +10,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept
standalone: false,
})
export class SetupProviderComponent extends BaseAcceptComponent {
protected logo = BitwardenLogo;
protected logo = Icons.BitwardenLogo;
failedShortMessage = "inviteAcceptFailedShort";
failedMessage = "inviteAcceptFailed";

View File

@@ -2,7 +2,7 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { unauthGuardFn } from "@bitwarden/angular/auth/guards";
import { AnonLayoutWrapperComponent } from "@bitwarden/auth/angular";
import { AnonLayoutWrapperComponent } from "@bitwarden/components";
import { deepLinkGuard } from "@bitwarden/web-vault/app/auth/guards/deep-link/deep-link.guard";
import { RouteDataProperties } from "@bitwarden/web-vault/app/core";

View File

@@ -3,7 +3,6 @@ import { ActivatedRoute, Params, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { filter, map, switchMap } from "rxjs/operators";
import { BitwardenLogo } from "@bitwarden/auth/angular";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
@@ -13,6 +12,7 @@ import { StateProvider } from "@bitwarden/common/platform/state";
import { SyncService } from "@bitwarden/common/platform/sync";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { ProviderKey } from "@bitwarden/common/types/key";
import { Icons } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service";
import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept.component";
@@ -22,7 +22,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept
standalone: false,
})
export class SetupBusinessUnitComponent extends BaseAcceptComponent {
protected bitwardenLogo = BitwardenLogo;
protected bitwardenLogo = Icons.BitwardenLogo;
failedMessage = "emergencyInviteAcceptFailed";
failedShortMessage = "emergencyInviteAcceptFailedShort";

View File

@@ -14,8 +14,6 @@ import {
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import {
AnonLayoutWrapperDataService,
DefaultAnonLayoutWrapperDataService,
DefaultLoginApprovalComponentService,
DefaultLoginComponentService,
DefaultLoginDecryptionOptionsService,
@@ -299,7 +297,11 @@ import { FolderService } from "@bitwarden/common/vault/services/folder/folder.se
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service";
import { DefaultTaskService, TaskService } from "@bitwarden/common/vault/tasks";
import { ToastService } from "@bitwarden/components";
import {
AnonLayoutWrapperDataService,
DefaultAnonLayoutWrapperDataService,
ToastService,
} from "@bitwarden/components";
import {
GeneratorHistoryService,
LocalGeneratorHistoryService,

View File

@@ -1,14 +1,18 @@
import { Injectable, inject } from "@angular/core";
import { Observable, combineLatest, from, of } from "rxjs";
import { catchError, map } from "rxjs/operators";
import { catchError, switchMap } from "rxjs/operators";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { UserId } from "@bitwarden/common/types/guid";
import { BiometricStateService } from "@bitwarden/key-management";
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
import { NudgeStatus, NudgeType } from "../nudges.service";
@@ -21,6 +25,9 @@ export class AccountSecurityNudgeService extends DefaultSingleNudgeService {
private logService = inject(LogService);
private pinService = inject(PinServiceAbstraction);
private vaultTimeoutSettingsService = inject(VaultTimeoutSettingsService);
private biometricStateService = inject(BiometricStateService);
private policyService = inject(PolicyService);
private organizationService = inject(OrganizationService);
nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> {
const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe(
@@ -36,16 +43,45 @@ export class AccountSecurityNudgeService extends DefaultSingleNudgeService {
this.getNudgeStatus$(nudgeType, userId),
of(Date.now() - THIRTY_DAYS_MS),
from(this.pinService.isPinSet(userId)),
from(this.vaultTimeoutSettingsService.isBiometricLockSet(userId)),
this.biometricStateService.biometricUnlockEnabled$,
this.organizationService.organizations$(userId),
this.policyService.policiesByType$(PolicyType.RemoveUnlockWithPin, userId),
]).pipe(
map(([profileCreationDate, status, profileCutoff, isPinSet, isBiometricLockSet]) => {
const profileOlderThanCutoff = profileCreationDate.getTime() < profileCutoff;
const hideNudge = profileOlderThanCutoff || isPinSet || isBiometricLockSet;
return {
hasBadgeDismissed: status.hasBadgeDismissed || hideNudge,
hasSpotlightDismissed: status.hasSpotlightDismissed || hideNudge,
};
}),
switchMap(
async ([
profileCreationDate,
status,
profileCutoff,
isPinSet,
biometricUnlockEnabled,
organizations,
policies,
]) => {
const profileOlderThanCutoff = profileCreationDate.getTime() < profileCutoff;
const hasOrgWithRemovePinPolicyOn = organizations.some((org) => {
return policies.some(
(p) => p.type === PolicyType.RemoveUnlockWithPin && p.organizationId === org.id,
);
});
const hideNudge =
profileOlderThanCutoff ||
isPinSet ||
biometricUnlockEnabled ||
hasOrgWithRemovePinPolicyOn;
const acctSecurityNudgeStatus = {
hasBadgeDismissed: status.hasBadgeDismissed || hideNudge,
hasSpotlightDismissed: status.hasSpotlightDismissed || hideNudge,
};
if (isPinSet || biometricUnlockEnabled || hasOrgWithRemovePinPolicyOn) {
await this.setNudgeStatus(nudgeType, acctSecurityNudgeStatus, userId);
}
return acctSecurityNudgeStatus;
},
),
);
}
}

View File

@@ -6,6 +6,8 @@ import { firstValueFrom, of } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@@ -13,6 +15,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { StateProvider } from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { BiometricStateService } from "@bitwarden/key-management";
import { FakeStateProvider, mockAccountServiceWith } from "../../../../../libs/common/spec";
@@ -91,6 +94,18 @@ describe("Vault Nudges Service", () => {
provide: VaultTimeoutSettingsService,
useValue: mock<VaultTimeoutSettingsService>(),
},
{
provide: BiometricStateService,
useValue: mock<BiometricStateService>(),
},
{
provide: PolicyService,
useValue: mock<PolicyService>(),
},
{
provide: OrganizationService,
useValue: mock<OrganizationService>(),
},
],
});
});

View File

@@ -1,8 +1,4 @@
export * from "./bitwarden-logo.icon";
export * from "./bitwarden-shield.icon";
export * from "./devices.icon";
export * from "./lock.icon";
export * from "./registration-check-email.icon";
export * from "./user-lock.icon";
export * from "./user-verification-biometrics-fingerprint.icon";
export * from "./wave.icon";

View File

@@ -1,13 +1,6 @@
/**
* This barrel file should only contain Angular exports
*/
// anon layout
export * from "./anon-layout/anon-layout.component";
export * from "./anon-layout/anon-layout-wrapper.component";
export * from "./anon-layout/anon-layout-wrapper-data.service";
export * from "./anon-layout/default-anon-layout-wrapper-data.service";
// change password
export * from "./change-password/change-password.component";
export * from "./change-password/change-password.service.abstraction";

View File

@@ -30,6 +30,7 @@ import { UserId } from "@bitwarden/common/types/guid";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import {
AnonLayoutWrapperDataService,
AsyncActionsModule,
ButtonModule,
CheckboxModule,
@@ -40,8 +41,6 @@ import {
} from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service";
import { LoginDecryptionOptionsService } from "./login-decryption-options.service";
// FIXME: update to use a const object instead of a typescript enum

View File

@@ -32,6 +32,7 @@ import { UserId } from "@bitwarden/common/types/guid";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import {
AnonLayoutWrapperDataService,
AsyncActionsModule,
ButtonModule,
CheckboxModule,
@@ -41,7 +42,6 @@ import {
ToastService,
} from "@bitwarden/components";
import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service";
import { VaultIcon, WaveIcon } from "../icons";
import { LoginComponentService, PasswordPolicies } from "./login-component.service";

View File

@@ -16,14 +16,13 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { ToastService } from "@bitwarden/components";
import { AnonLayoutWrapperDataService, ToastService } from "@bitwarden/components";
import {
LoginStrategyServiceAbstraction,
LoginSuccessHandlerService,
PasswordLoginCredentials,
} from "../../../common";
import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service";
import {
InputPasswordComponent,
InputPasswordFlow,

View File

@@ -14,18 +14,18 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import {
AnonLayoutWrapperDataService,
AsyncActionsModule,
ButtonModule,
CheckboxModule,
FormFieldModule,
Icons,
IconModule,
LinkModule,
} from "@bitwarden/components";
import { LoginEmailService } from "../../../common";
import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service";
import { RegistrationUserAddIcon } from "../../icons";
import { RegistrationCheckEmailIcon } from "../../icons/registration-check-email.icon";
import { RegistrationEnvSelectorComponent } from "../registration-env-selector/registration-env-selector.component";
// FIXME: update to use a const object instead of a typescript enum
@@ -170,7 +170,7 @@ export class RegistrationStartComponent implements OnInit, OnDestroy {
pageTitle: {
key: "checkYourEmail",
},
pageIcon: RegistrationCheckEmailIcon,
pageIcon: Icons.RegistrationCheckEmailIcon,
});
this.registrationStartStateChange.emit(this.state);
};

View File

@@ -18,6 +18,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import {
AnonLayoutWrapperData,
AnonLayoutWrapperDataService,
AsyncActionsModule,
ButtonModule,
DialogModule,
@@ -34,8 +36,6 @@ import {
// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports
import { PreloadedEnglishI18nModule } from "../../../../../../apps/web/src/app/core/tests";
import { LoginEmailService } from "../../../common";
import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service";
import { AnonLayoutWrapperData } from "../../anon-layout/anon-layout-wrapper.component";
import { RegistrationStartComponent } from "./registration-start.component";

View File

@@ -36,9 +36,7 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp
import { UserId } from "@bitwarden/common/types/guid";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { DialogService, ToastService } from "@bitwarden/components";
import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service";
import { DialogService, ToastService, AnonLayoutWrapperDataService } from "@bitwarden/components";
import { TwoFactorAuthComponentCacheService } from "./two-factor-auth-component-cache.service";
import { TwoFactorAuthComponentService } from "./two-factor-auth-component.service";

View File

@@ -41,6 +41,7 @@ import { UserId } from "@bitwarden/common/types/guid";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import {
AnonLayoutWrapperDataService,
AsyncActionsModule,
ButtonModule,
CheckboxModule,
@@ -49,7 +50,6 @@ import {
ToastService,
} from "@bitwarden/components";
import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service";
import {
TwoFactorAuthAuthenticatorIcon,
TwoFactorAuthEmailIcon,

View File

@@ -92,6 +92,27 @@ describe("FidoAuthenticatorService", () => {
});
describe("createCredential", () => {
describe("Mapping params should handle variations in input formats", () => {
it.each([
[true, true],
[false, false],
["false", false],
["", false],
["true", true],
])("requireResidentKey should handle %s as boolean %s", async (input, expected) => {
const params = createParams({
authenticatorSelection: { requireResidentKey: input as any },
extensions: { credProps: true },
});
authenticator.makeCredential.mockResolvedValue(createAuthenticatorMakeResult());
const result = await client.createCredential(params, windowReference);
expect(result.extensions.credProps?.rk).toBe(expected);
});
});
describe("input parameters validation", () => {
// Spec: If sameOriginWithAncestors is false, return a "NotAllowedError" DOMException.
it("should throw error if sameOriginWithAncestors is false", async () => {

View File

@@ -483,11 +483,15 @@ function mapToMakeCredentialParams({
type: credential.type,
})) ?? [];
/**
* Quirk: Accounts for the fact that some RP's mistakenly submits 'requireResidentKey' as a string
*/
const requireResidentKey =
params.authenticatorSelection?.residentKey === "required" ||
params.authenticatorSelection?.residentKey === "preferred" ||
(params.authenticatorSelection?.residentKey === undefined &&
params.authenticatorSelection?.requireResidentKey === true);
(params.authenticatorSelection?.requireResidentKey === true ||
(params.authenticatorSelection?.requireResidentKey as unknown as string) === "true"));
const requireUserVerification =
params.authenticatorSelection?.userVerification === "required" ||

View File

@@ -1,6 +1,45 @@
// FIXME: Update this file to be type safe and remove this and next line
import type {
AssertCredentialResult,
CreateCredentialResult,
} from "../../abstractions/fido2/fido2-client.service.abstraction";
// @ts-strict-ignore
export class Fido2Utils {
static createResultToJson(result: CreateCredentialResult): any {
return {
id: result.credentialId,
rawId: result.credentialId,
response: {
clientDataJSON: result.clientDataJSON,
authenticatorData: result.authData,
transports: result.transports,
publicKey: result.publicKey,
publicKeyAlgorithm: result.publicKeyAlgorithm,
attestationObject: result.attestationObject,
},
authenticatorAttachment: "platform",
clientExtensionResults: result.extensions,
type: "public-key",
};
}
static getResultToJson(result: AssertCredentialResult): any {
return {
id: result.credentialId,
rawId: result.credentialId,
response: {
clientDataJSON: result.clientDataJSON,
authenticatorData: result.authenticatorData,
signature: result.signature,
userHandle: result.userHandle,
},
authenticatorAttachment: "platform",
clientExtensionResults: {},
type: "public-key",
};
}
static bufferToString(bufferSource: BufferSource): string {
return Fido2Utils.fromBufferToB64(Fido2Utils.bufferSourceToUint8Array(bufferSource))
.replace(/\+/g, "-")

View File

@@ -4,13 +4,13 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router";
import { Subject, filter, switchMap, takeUntil, tap } from "rxjs";
import { AnonLayoutComponent } from "@bitwarden/auth/angular";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { Icon, Translation } from "@bitwarden/components";
import { Translation } from "../dialog";
import { Icon } from "../icon";
import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service";
import { AnonLayoutComponent } from "./anon-layout.component";
export interface AnonLayoutWrapperData {
/**

View File

@@ -14,24 +14,19 @@ import {
EnvironmentService,
Environment,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { ButtonModule } from "@bitwarden/components";
// FIXME: remove `/apps` import from `/libs`
// FIXME: remove `src` and fix import
// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports
import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests";
import { LockIcon } from "../icons";
import { RegistrationCheckEmailIcon } from "../icons/registration-check-email.icon";
import { ButtonModule } from "../button";
import { LockIcon, RegistrationCheckEmailIcon } from "../icon/icons";
import { I18nMockService } from "../utils";
import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "./anon-layout-wrapper.component";
import { DefaultAnonLayoutWrapperDataService } from "./default-anon-layout-wrapper-data.service";
export default {
title: "Auth/Anon Layout Wrapper",
title: "Component Library/Anon Layout Wrapper",
component: AnonLayoutWrapperComponent,
} as Meta;
@@ -84,13 +79,21 @@ const decorators = (options: {
getClientType: () => options.clientType || ClientType.Web,
} as Partial<PlatformUtilsService>,
},
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({
setAStrongPassword: "Set a strong password",
appLogoLabel: "app logo label",
finishCreatingYourAccountBySettingAPassword:
"Finish creating your account by setting a password",
});
},
},
],
}),
applicationConfig({
providers: [
importProvidersFrom(RouterModule.forRoot(options.routes)),
importProvidersFrom(PreloadedEnglishI18nModule),
],
providers: [importProvidersFrom(RouterModule.forRoot(options.routes))],
}),
];
};
@@ -102,18 +105,21 @@ type Story = StoryObj<AnonLayoutWrapperComponent>;
@Component({
selector: "bit-default-primary-outlet-example-component",
template: "<p>Primary Outlet Example: <br> your primary component goes here</p>",
standalone: false,
})
export class DefaultPrimaryOutletExampleComponent {}
@Component({
selector: "bit-default-secondary-outlet-example-component",
template: "<p>Secondary Outlet Example: <br> your secondary component goes here</p>",
standalone: false,
})
export class DefaultSecondaryOutletExampleComponent {}
@Component({
selector: "bit-default-env-selector-outlet-example-component",
template: "<p>Env Selector Outlet Example: <br> your env selector component goes here</p>",
standalone: false,
})
export class DefaultEnvSelectorOutletExampleComponent {}
@@ -188,6 +194,7 @@ const changedData: AnonLayoutWrapperData = {
template: `
<button type="button" bitButton buttonType="primary" (click)="toggleData()">Toggle Data</button>
`,
standalone: false,
})
export class DynamicContentExampleComponent {
initialData = true;

View File

@@ -9,16 +9,10 @@ import { ClientType } from "@bitwarden/common/enums";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { IconModule, Icon } from "../../../../components/src/icon";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { SharedModule } from "../../../../components/src/shared";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { TypographyModule } from "../../../../components/src/typography";
import { BitwardenLogo, BitwardenShield } from "../icons";
import { IconModule, Icon } from "../icon";
import { BitwardenLogo, BitwardenShield } from "../icon/icons";
import { SharedModule } from "../shared";
import { TypographyModule } from "../typography";
@Component({
selector: "auth-anon-layout",

View File

@@ -6,8 +6,8 @@ import * as stories from "./anon-layout.stories";
# AnonLayout Component
The Auth-owned AnonLayoutComponent is to be used primarily for unauthenticated pages\*, where we
don't know who the user is.
The AnonLayoutComponent is to be used primarily for unauthenticated pages\*, where we don't know who
the user is.
\*There will be a few exceptions to this&mdash;that is, AnonLayout will also be used for the Unlock
and View Send pages.

View File

@@ -7,13 +7,9 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { ButtonModule } from "../../../../components/src/button";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { I18nMockService } from "../../../../components/src/utils/i18n-mock.service";
import { LockIcon } from "../icons";
import { ButtonModule } from "../button";
import { LockIcon } from "../icon/icons";
import { I18nMockService } from "../utils/i18n-mock.service";
import { AnonLayoutComponent } from "./anon-layout.component";
@@ -23,7 +19,7 @@ class MockPlatformUtilsService implements Partial<PlatformUtilsService> {
}
export default {
title: "Auth/Anon Layout",
title: "Component Library/Anon Layout",
component: AnonLayoutComponent,
decorators: [
moduleMetadata({
@@ -38,6 +34,7 @@ export default {
useFactory: () => {
return new I18nMockService({
accessing: "Accessing",
appLogoLabel: "app logo label",
});
},
},

View File

@@ -0,0 +1,4 @@
export * from "./anon-layout-wrapper-data.service";
export * from "./anon-layout-wrapper.component";
export * from "./anon-layout.component";
export * from "./default-anon-layout-wrapper-data.service";

View File

@@ -1,6 +1,4 @@
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { svgIcon } from "@bitwarden/components";
import { svgIcon } from "../icon";
export const BitwardenLogo = svgIcon`
<svg viewBox="0 0 290 45" xmlns="http://www.w3.org/2000/svg">

View File

@@ -1,6 +1,4 @@
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { svgIcon } from "@bitwarden/components";
import { svgIcon } from "../icon";
export const BitwardenShield = svgIcon`
<svg viewBox="0 0 120 132" xmlns="http://www.w3.org/2000/svg">

View File

@@ -1,4 +1,4 @@
import { svgIcon } from "@bitwarden/components";
import { svgIcon } from "../icon";
export const ExtensionBitwardenLogo = svgIcon`
<svg

View File

@@ -1,8 +1,13 @@
export * from "./search";
export * from "./security";
export * from "./bitwarden-logo.icon";
export * from "./bitwarden-shield.icon";
export * from "./extension-bitwarden-logo.icon";
export * from "./lock.icon";
export * from "./generator";
export * from "./no-access";
export * from "./no-results";
export * from "./generator";
export * from "./registration-check-email.icon";
export * from "./search";
export * from "./security";
export * from "./send";
export * from "./settings";
export * from "./vault";

View File

@@ -1,6 +1,4 @@
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { svgIcon } from "@bitwarden/components";
import { svgIcon } from "../icon";
export const LockIcon = svgIcon`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 100" fill="none">

View File

@@ -1,6 +1,4 @@
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { svgIcon } from "@bitwarden/components";
import { svgIcon } from "../icon";
export const RegistrationCheckEmailIcon = svgIcon`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 100" fill="none">

View File

@@ -1,5 +1,6 @@
export { ButtonType, ButtonLikeAbstraction } from "./shared/button-like.abstraction";
export * from "./a11y";
export * from "./anon-layout";
export * from "./async-actions";
export * from "./avatar";
export * from "./badge-list";

View File

@@ -16,7 +16,6 @@ import {
} from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular";
import { LogoutService, PinServiceAbstraction } from "@bitwarden/auth/common";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
@@ -42,6 +41,7 @@ import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/pass
import { UserKey } from "@bitwarden/common/types/key";
import {
AsyncActionsModule,
AnonLayoutWrapperDataService,
ButtonModule,
DialogService,
FormFieldModule,

60
package-lock.json generated
View File

@@ -85,7 +85,7 @@
"@babel/core": "7.24.9",
"@babel/preset-env": "7.24.8",
"@compodoc/compodoc": "1.1.26",
"@electron/notarize": "2.5.0",
"@electron/notarize": "3.0.1",
"@electron/rebuild": "3.7.2",
"@eslint/compat": "1.2.9",
"@lit-labs/signals": "0.1.2",
@@ -5402,34 +5402,17 @@
}
},
"node_modules/@electron/notarize": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz",
"integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-3.0.1.tgz",
"integrity": "sha512-5xzcOwvMGNjkSk7s0sPx4XcKWei9FYk4f2S5NkSorWW0ce5yktTOtlPa0W5yQHcREILh+C3JdH+t+M637g9TmQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"fs-extra": "^9.0.1",
"debug": "^4.4.0",
"promise-retry": "^2.0.1"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/@electron/notarize/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
"node": ">= 22.12.0"
}
},
"node_modules/@electron/osx-sign": {
@@ -14324,6 +14307,37 @@
"electron-builder-squirrel-windows": "26.0.12"
}
},
"node_modules/app-builder-lib/node_modules/@electron/notarize": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz",
"integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"fs-extra": "^9.0.1",
"promise-retry": "^2.0.1"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/app-builder-lib/node_modules/@electron/notarize/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/app-builder-lib/node_modules/@electron/rebuild": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.7.0.tgz",

View File

@@ -45,7 +45,7 @@
"@babel/core": "7.24.9",
"@babel/preset-env": "7.24.8",
"@compodoc/compodoc": "1.1.26",
"@electron/notarize": "2.5.0",
"@electron/notarize": "3.0.1",
"@electron/rebuild": "3.7.2",
"@eslint/compat": "1.2.9",
"@lit-labs/signals": "0.1.2",