1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

Merge branch 'master' of https://github.com/bitwarden/clients into tools/pm-147/import-error-states-usability-improvements

This commit is contained in:
Daniel James Smith
2023-09-01 15:34:26 +02:00
861 changed files with 65826 additions and 15755 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
"version": "2023.7.0",
"version": "2023.8.2",
"scripts": {
"build:oss": "webpack",
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",

View File

@@ -1,9 +1,9 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { DialogService } from "@bitwarden/components";
@Injectable({
providedIn: "root",
@@ -13,7 +13,7 @@ export class IsPaidOrgGuard implements CanActivate {
private router: Router,
private organizationService: OrganizationService,
private messagingService: MessagingService,
private dialogService: DialogServiceAbstraction
private dialogService: DialogService
) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
@@ -31,7 +31,7 @@ export class IsPaidOrgGuard implements CanActivate {
content: { key: "notAvailableForFreeOrganization" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: SimpleDialogType.INFO,
type: "info",
});
return false;
} else {

View File

@@ -3,7 +3,6 @@ import { Component, Inject, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { EventResponse } from "@bitwarden/common/models/response/event.response";
@@ -13,7 +12,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { TableDataSource } from "@bitwarden/components";
import { DialogService, TableDataSource } from "@bitwarden/components";
import { EventService } from "../../../core";
import { SharedModule } from "../../../shared";
@@ -186,7 +185,7 @@ export class EntityEventsComponent implements OnInit {
* @param config Configuration for the dialog
*/
export const openEntityEventsDialog = (
dialogService: DialogServiceAbstraction,
dialogService: DialogService,
config: DialogConfig<EntityEventsDialogParams>
) => {
return dialogService.open<void, EntityEventsDialogParams>(EntityEventsComponent, config);

View File

@@ -3,7 +3,6 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from "@angula
import { FormBuilder, Validators } from "@angular/forms";
import { catchError, combineLatest, from, map, of, Subject, switchMap, takeUntil } from "rxjs";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
@@ -14,6 +13,7 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti
import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data";
import { Collection } from "@bitwarden/common/vault/models/domain/collection";
import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/response/collection.response";
import { DialogService } from "@bitwarden/components";
import { InternalGroupService as GroupService, GroupView } from "../core";
import {
@@ -64,7 +64,7 @@ export enum GroupAddEditDialogResultType {
* @param config Configuration for the dialog
*/
export const openGroupAddEditDialog = (
dialogService: DialogServiceAbstraction,
dialogService: DialogService,
config: DialogConfig<GroupAddEditDialogParams>
) => {
return dialogService.open<GroupAddEditDialogResultType, GroupAddEditDialogParams>(
@@ -181,7 +181,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
private logService: LogService,
private formBuilder: FormBuilder,
private changeDetectorRef: ChangeDetectorRef,
private dialogService: DialogServiceAbstraction
private dialogService: DialogService
) {
this.tabIndex = params.initialTab ?? GroupAddEditTabType.Info;
}
@@ -273,7 +273,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
const confirmed = await this.dialogService.openSimpleDialog({
title: this.group.name,
content: { key: "deleteGroupConfirmation" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
return false;

View File

@@ -15,7 +15,6 @@ import {
import { first } from "rxjs/operators";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
@@ -32,6 +31,7 @@ import {
CollectionResponse,
} from "@bitwarden/common/vault/models/response/collection.response";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { DialogService } from "@bitwarden/components";
import { InternalGroupService as GroupService, GroupView } from "../core";
@@ -127,7 +127,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
private route: ActivatedRoute,
private i18nService: I18nService,
private modalService: ModalService,
private dialogService: DialogServiceAbstraction,
private dialogService: DialogService,
private platformUtilsService: PlatformUtilsService,
private searchService: SearchService,
private logService: LogService,
@@ -236,7 +236,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
const confirmed = await this.dialogService.openSimpleDialog({
title: groupRow.details.name,
content: { key: "deleteGroupConfirmation" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
return false;
@@ -269,7 +269,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
placeholders: [groupsToDelete.length.toString()],
},
content: deleteMessage,
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
return false;

View File

@@ -28,10 +28,7 @@ export class UserConfirmComponent implements OnInit {
async ngOnInit() {
try {
if (this.publicKey != null) {
const fingerprint = await this.cryptoService.getFingerprint(
this.userId,
this.publicKey.buffer
);
const fingerprint = await this.cryptoService.getFingerprint(this.userId, this.publicKey);
if (fingerprint != null) {
this.fingerprint = fingerprint.join("-");
}

View File

@@ -7,6 +7,7 @@ import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enum
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { BulkUserDetails } from "./bulk-status.component";
@@ -47,7 +48,7 @@ export class BulkConfirmComponent implements OnInit {
for (const entry of response.data) {
const publicKey = Utils.fromB64ToArray(entry.key);
const fingerprint = await this.cryptoService.getFingerprint(entry.userId, publicKey.buffer);
const fingerprint = await this.cryptoService.getFingerprint(entry.userId, publicKey);
if (fingerprint != null) {
this.publicKeys.set(entry.id, publicKey);
this.fingerprints.set(entry.id, fingerprint.join("-"));
@@ -67,7 +68,7 @@ export class BulkConfirmComponent implements OnInit {
if (publicKey == null) {
continue;
}
const encryptedKey = await this.cryptoService.rsaEncrypt(key.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(key.key, publicKey);
userIdsWithKeys.push({
id: user.id,
key: encryptedKey.encryptedString,
@@ -98,7 +99,7 @@ export class BulkConfirmComponent implements OnInit {
);
}
protected getCryptoKey() {
protected getCryptoKey(): Promise<SymmetricCryptoKey> {
return this.cryptoService.getOrgKey(this.organizationId);
}

View File

@@ -1,11 +1,10 @@
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject, OnInit } from "@angular/core";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { TableDataSource } from "@bitwarden/components";
import { DialogService, TableDataSource } from "@bitwarden/components";
import { OrganizationUserView } from "../../../core";
@@ -44,7 +43,7 @@ export class BulkEnableSecretsManagerDialogComponent implements OnInit {
this.dialogRef.close();
};
static open(dialogService: DialogServiceAbstraction, data: BulkEnableSecretsManagerDialogData) {
static open(dialogService: DialogService, data: BulkEnableSecretsManagerDialogData) {
return dialogService.open<unknown, BulkEnableSecretsManagerDialogData>(
BulkEnableSecretsManagerDialogComponent,
{ data }

View File

@@ -246,7 +246,7 @@
(change)="handleDependentPermissions()"
/>
<label class="!tw-font-normal" for="manageResetPassword">
{{ "manageResetPassword" | i18n }}
{{ "manageAccountRecovery" | i18n }}
</label>
</div>
</div>

View File

@@ -3,7 +3,6 @@ import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { combineLatest, of, shareReplay, Subject, switchMap, takeUntil } from "rxjs";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
@@ -15,6 +14,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { DialogService } from "@bitwarden/components";
import { flagEnabled } from "../../../../../../utils/flags";
import { CollectionAdminService } from "../../../../../vault/core/collection-admin.service";
@@ -134,7 +134,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
private groupService: GroupService,
private userService: UserAdminService,
private organizationUserService: OrganizationUserService,
private dialogService: DialogServiceAbstraction
private dialogService: DialogService
) {}
async ngOnInit() {
@@ -302,7 +302,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
}
handleDependentPermissions() {
// Manage Password Reset must have Manage Users enabled
// Manage Password Reset (Account Recovery) must have Manage Users enabled
if (
this.permissionsGroup.value.manageResetPassword &&
!this.permissionsGroup.value.manageUsers
@@ -312,7 +312,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("resetPasswordManageUsers")
this.i18nService.t("accountRecoveryManageUsers")
);
}
}
@@ -389,7 +389,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
let confirmed = await this.dialogService.openSimpleDialog({
title: { key: "removeUserIdAccess", placeholders: [this.params.name] },
content: { key: message },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -426,7 +426,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
title: { key: "revokeUserId", placeholders: [this.params.name] },
content: { key: "revokeUserConfirmation" },
acceptButtonText: { key: "revokeAccess" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -496,7 +496,7 @@ export class MemberDialogComponent implements OnInit, OnDestroy {
key: "removeOrgUserNoMasterPasswordDesc",
placeholders: [this.params.name],
},
type: SimpleDialogType.WARNING,
type: "warning",
});
}
}
@@ -557,7 +557,7 @@ function mapToGroupAccessSelections(groups: string[]): AccessItemValue[] {
* @param config Configuration for the dialog
*/
export function openUserAddEditDialog(
dialogService: DialogServiceAbstraction,
dialogService: DialogService,
config: DialogConfig<MemberDialogParams>
) {
return dialogService.open<MemberDialogResult, MemberDialogParams>(MemberDialogComponent, config);

View File

@@ -10,7 +10,6 @@ import {
import { Subject, takeUntil } from "rxjs";
import zxcvbn from "zxcvbn";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { PasswordStrengthComponent } from "@bitwarden/angular/shared/components/password-strength/password-strength.component";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { OrganizationUserResetPasswordRequest } from "@bitwarden/common/abstractions/organization-user/requests";
@@ -23,8 +22,12 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "app-reset-password",
@@ -54,7 +57,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
private cryptoService: CryptoService,
private logService: LogService,
private organizationUserService: OrganizationUserService,
private dialogService: DialogServiceAbstraction
private dialogService: DialogService
) {}
async ngOnInit() {
@@ -140,7 +143,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakMasterPassword" },
content: { key: "weakMasterPasswordDesc" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!result) {
@@ -171,26 +174,32 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
orgSymKey
);
// Decrypt User's Reset Password Key to get EncKey
// Decrypt User's Reset Password Key to get UserKey
const decValue = await this.cryptoService.rsaDecrypt(resetPasswordKey, decPrivateKey);
const userEncKey = new SymmetricCryptoKey(decValue);
const existingUserKey = new SymmetricCryptoKey(decValue) as UserKey;
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(
// Create new master key and hash new password
const newMasterKey = await this.cryptoService.makeMasterKey(
this.newPassword,
this.email.trim().toLowerCase(),
kdfType,
new KdfConfig(kdfIterations, kdfMemory, kdfParallelism)
);
const newPasswordHash = await this.cryptoService.hashPassword(this.newPassword, newKey);
const newMasterKeyHash = await this.cryptoService.hashMasterKey(
this.newPassword,
newMasterKey
);
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
// Create new encrypted user key for the User
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(
newMasterKey,
existingUserKey
);
// Create request
const request = new OrganizationUserResetPasswordRequest();
request.key = newEncKey[1].encryptedString;
request.newMasterPasswordHash = newPasswordHash;
request.key = newUserKey[1].encryptedString;
request.newMasterPasswordHash = newMasterKeyHash;
// Change user's password
return this.organizationUserService.putOrganizationUserResetPassword(

View File

@@ -16,12 +16,6 @@ import {
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import {
DialogServiceAbstraction,
SimpleDialogCloseType,
SimpleDialogOptions,
SimpleDialogType,
} from "@bitwarden/angular/services/dialog";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
@@ -55,6 +49,7 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data";
import { Collection } from "@bitwarden/common/vault/models/domain/collection";
import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/response/collection.response";
import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
import { flagEnabled } from "../../../../utils/flags";
import { openEntityEventsDialog } from "../../../admin-console/organizations/manage/entity-events.component";
@@ -125,7 +120,7 @@ export class PeopleComponent
private organizationService: OrganizationService,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService,
dialogService: DialogServiceAbstraction,
dialogService: DialogService,
private router: Router,
private groupService: GroupService,
private collectionService: CollectionService
@@ -302,7 +297,7 @@ export class PeopleComponent
async confirmUser(user: OrganizationUserView, publicKey: Uint8Array): Promise<void> {
const orgKey = await this.cryptoService.getOrgKey(this.organization.id);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey);
const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString;
await this.organizationUserService.postOrganizationUserConfirm(
@@ -359,7 +354,7 @@ export class PeopleComponent
: "freeOrgInvLimitReachedNoManageBilling",
this.organization.seats
),
type: SimpleDialogType.PRIMARY,
type: "primary",
};
if (this.organization.canEditSubscription) {
@@ -371,12 +366,12 @@ export class PeopleComponent
const simpleDialog = this.dialogService.openSimpleDialogRef(orgUpgradeSimpleDialogOpts);
firstValueFrom(simpleDialog.closed).then((result: SimpleDialogCloseType | undefined) => {
firstValueFrom(simpleDialog.closed).then((result: boolean | undefined) => {
if (!result) {
return;
}
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canEditSubscription) {
if (result && this.organization.canEditSubscription) {
this.router.navigate(["/organizations", this.organization.id, "billing", "subscription"], {
queryParams: { upgrade: true },
});
@@ -581,7 +576,7 @@ export class PeopleComponent
placeholders: [this.userNamePipe.transform(user)],
},
content: { key: content },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -600,7 +595,7 @@ export class PeopleComponent
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
content: this.revokeWarningMessage(),
acceptButtonText: { key: "revokeAccess" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -676,7 +671,7 @@ export class PeopleComponent
key: "removeOrgUserNoMasterPasswordDesc",
placeholders: [this.userNamePipe.transform(user)],
},
type: SimpleDialogType.WARNING,
type: "warning",
});
}
}

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "@bitwarden/angular/auth/guards/auth.guard";
import { AuthGuard } from "@bitwarden/angular/auth/guards";
import {
canAccessOrgAdmin,
canAccessGroupsTab,

View File

@@ -3,7 +3,6 @@ import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { combineLatest, lastValueFrom, Subject, switchMap, takeUntil, from } from "rxjs";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@@ -15,6 +14,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components";
import { ApiKeyComponent } from "../../../settings/api-key.component";
import { PurgeVaultComponent } from "../../../settings/purge-vault.component";
@@ -61,7 +61,7 @@ export class AccountComponent {
});
protected organizationId: string;
protected publicKeyBuffer: ArrayBuffer;
protected publicKeyBuffer: Uint8Array;
private destroy$ = new Subject<void>();
@@ -75,7 +75,7 @@ export class AccountComponent {
private router: Router,
private organizationService: OrganizationService,
private organizationApiService: OrganizationApiServiceAbstraction,
private dialogService: DialogServiceAbstraction,
private dialogService: DialogService,
private formBuilder: FormBuilder
) {}
@@ -106,7 +106,7 @@ export class AccountComponent {
this.org = orgResponse;
// Public Key Buffer for Org Fingerprint Generation
this.publicKeyBuffer = Utils.fromB64ToArray(orgKeys?.publicKey)?.buffer;
this.publicKeyBuffer = Utils.fromB64ToArray(orgKeys?.publicKey);
// Patch existing values
this.formGroup.patchValue({

View File

@@ -3,7 +3,6 @@ import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, FormControl, Validators } from "@angular/forms";
import { combineLatest, Subject, takeUntil } from "rxjs";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
@@ -15,6 +14,7 @@ import { Verification } from "@bitwarden/common/types/verification";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { DialogService } from "@bitwarden/components";
import { UserVerificationModule } from "../../../../auth/shared/components/user-verification";
import { SharedModule } from "../../../../shared/shared.module";
@@ -168,7 +168,7 @@ export class DeleteOrganizationDialogComponent implements OnInit, OnDestroy {
* @param config Configuration for the dialog
*/
export function openDeleteOrganizationDialog(
dialogService: DialogServiceAbstraction,
dialogService: DialogService,
config: DialogConfig<DeleteOrganizationDialogParams>
) {
return dialogService.open<DeleteOrganizationDialogResult, DeleteOrganizationDialogParams>(

View File

@@ -70,6 +70,9 @@ function getSettingsRoute(organization: Organization) {
if (organization.canManageScim) {
return "scim";
}
if (organization.canManageDeviceApprovals) {
return "device-approvals";
}
return undefined;
}

View File

@@ -65,7 +65,7 @@
routerLink="device-approvals"
class="list-group-item"
routerLinkActive="active"
*ngIf="org.canManageUsersPassword"
*ngIf="org.canManageDeviceApprovals"
>
{{ "deviceApprovals" | i18n }}
</a>

View File

@@ -3,7 +3,6 @@ import { ActivatedRoute, Router } from "@angular/router";
import { lastValueFrom, Observable, Subject } from "rxjs";
import { first, map, takeUntil } from "rxjs/operators";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
@@ -14,6 +13,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { OrganizationPlansComponent } from "../../../billing/settings/organization-plans.component";
import {
@@ -62,7 +62,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy {
private syncService: SyncService,
private validationService: ValidationService,
private organizationService: OrganizationService,
private dialogService: DialogServiceAbstraction
private dialogService: DialogService
) {}
async ngOnInit() {

View File

@@ -2,7 +2,6 @@ import { Component } from "@angular/core";
import { UntypedFormBuilder } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
@@ -12,6 +11,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export";
import { ExportComponent } from "../../../../tools/import-export/export.component";
@@ -34,7 +34,7 @@ export class OrganizationExportComponent extends ExportComponent {
userVerificationService: UserVerificationService,
formBuilder: UntypedFormBuilder,
fileDownloadService: FileDownloadService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
cryptoService,

View File

@@ -1,8 +1,8 @@
import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { switchMap, takeUntil } from "rxjs/operators";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import {
canAccessVaultTab,
@@ -13,7 +13,10 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { ImportServiceAbstraction } from "@bitwarden/importer";
import { ImportComponent } from "../../../../tools/import-export/import.component";
@@ -37,11 +40,14 @@ export class OrganizationImportComponent extends ImportComponent {
private route: ActivatedRoute,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
private organizationService: OrganizationService,
organizationService: OrganizationService,
logService: LogService,
modalService: ModalService,
syncService: SyncService,
dialogService: DialogServiceAbstraction
dialogService: DialogService,
folderService: FolderService,
collectionService: CollectionService,
formBuilder: FormBuilder
) {
super(
i18nService,
@@ -52,7 +58,11 @@ export class OrganizationImportComponent extends ImportComponent {
logService,
modalService,
syncService,
dialogService
dialogService,
folderService,
collectionService,
organizationService,
formBuilder
);
}
@@ -74,20 +84,19 @@ export class OrganizationImportComponent extends ImportComponent {
await this.router.navigate(["organizations", this.organizationId, "vault"]);
} else {
this.fileSelected = null;
this.fileContents = "";
}
}
async submit() {
protected async performImport() {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "warning" },
content: { key: "importWarning", placeholders: [this.organization.name] },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
return;
}
super.submit();
await super.performImport();
}
}

View File

@@ -58,8 +58,8 @@ export class EnrollMasterPasswordReset {
const publicKey = Utils.fromB64ToArray(orgKeys.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const userKey = await this.cryptoService.getUserKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey);
keyString = encryptedKey.encryptedString;
toastStringRef = "enrollPasswordResetSuccess";

View File

@@ -2,12 +2,12 @@ import { formatDate } from "@angular/common";
import { Component, EventEmitter, Input, Output, OnInit } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "[sponsoring-org-row]",
@@ -32,7 +32,7 @@ export class SponsoringOrgRowComponent implements OnInit {
private i18nService: I18nService,
private logService: LogService,
private platformUtilsService: PlatformUtilsService,
private dialogService: DialogServiceAbstraction
private dialogService: DialogService
) {}
async ngOnInit() {
@@ -73,7 +73,7 @@ export class SponsoringOrgRowComponent implements OnInit {
title: `${this.i18nService.t("remove")} ${this.sponsoringOrg.familySponsorshipFriendlyName}?`,
content: { key: "revokeSponsorshipConfirmation" },
acceptButtonText: { key: "remove" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {

View File

@@ -7,12 +7,11 @@ import { IndividualConfig, ToastrService } from "ngx-toastr";
import { Subject, takeUntil } from "rxjs";
import Swal from "sweetalert2";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
@@ -27,6 +26,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { PolicyListService } from "./admin-console/core/policy-list.service";
import {
@@ -82,7 +82,7 @@ export class AppComponent implements OnDestroy, OnInit {
protected policyListService: PolicyListService,
private keyConnectorService: KeyConnectorService,
private configService: ConfigServiceAbstraction,
private dialogService: DialogServiceAbstraction
private dialogService: DialogService
) {}
ngOnInit() {
@@ -145,7 +145,7 @@ export class AppComponent implements OnDestroy, OnInit {
title: { key: "upgradeOrganization" },
content: { key: "upgradeOrganizationDesc" },
acceptButtonText: { key: "upgradeOrganization" },
type: SimpleDialogType.INFO,
type: "info",
});
if (upgradeConfirmed) {
this.router.navigate([
@@ -162,7 +162,7 @@ export class AppComponent implements OnDestroy, OnInit {
title: { key: "premiumRequired" },
content: { key: "premiumRequiredDesc" },
acceptButtonText: { key: "upgrade" },
type: SimpleDialogType.SUCCESS,
type: "success",
});
if (premiumConfirmed) {
this.router.navigate(["settings/subscription/premium"]);
@@ -174,7 +174,7 @@ export class AppComponent implements OnDestroy, OnInit {
title: { key: "emailVerificationRequired" },
content: { key: "emailVerificationRequiredDesc" },
acceptButtonText: { key: "learnMore" },
type: SimpleDialogType.INFO,
type: "info",
});
if (emailVerificationConfirmed) {
this.platformUtilsService.launchUri(

View File

@@ -9,6 +9,7 @@ import { AppComponent } from "./app.component";
import { CoreModule } from "./core";
import { OssRoutingModule } from "./oss-routing.module";
import { OssModule } from "./oss.module";
import { SendComponent } from "./tools/send/send.component";
import { WildcardRoutingModule } from "./wildcard-routing.module";
@NgModule({
@@ -21,6 +22,7 @@ import { WildcardRoutingModule } from "./wildcard-routing.module";
DragDropModule,
LayoutModule,
OssRoutingModule,
SendComponent,
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
],
declarations: [AppComponent],

View File

@@ -18,6 +18,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrgKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { BaseAcceptComponent } from "../common/base.accept.component";
@@ -108,16 +109,14 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
const request = new OrganizationUserAcceptInitRequest();
request.token = qParams.token;
const [encryptedOrgShareKey, orgShareKey] = await this.cryptoService.makeShareKey();
const [orgPublicKey, encryptedOrgPrivateKey] = await this.cryptoService.makeKeyPair(
orgShareKey
);
const [encryptedOrgKey, orgKey] = await this.cryptoService.makeOrgKey<OrgKey>();
const [orgPublicKey, encryptedOrgPrivateKey] = await this.cryptoService.makeKeyPair(orgKey);
const collection = await this.cryptoService.encrypt(
this.i18nService.t("defaultCollection"),
orgShareKey
orgKey
);
request.key = encryptedOrgShareKey.encryptedString;
request.key = encryptedOrgKey.encryptedString;
request.keys = new OrganizationKeysRequest(
orgPublicKey,
encryptedOrgPrivateKey.encryptedString
@@ -141,8 +140,8 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const userKey = await this.cryptoService.getUserKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey);
// Add reset password key to accept request
request.resetPasswordKey = encryptedKey.encryptedString;

View File

@@ -2,13 +2,13 @@ import { Component, NgZone } from "@angular/core";
import { Router } from "@angular/router";
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -17,6 +17,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { DialogService } from "@bitwarden/components";
import { RouterService } from "../core";
@@ -38,12 +39,13 @@ export class LockComponent extends BaseLockComponent {
stateService: StateService,
apiService: ApiService,
logService: LogService,
keyConnectorService: KeyConnectorService,
ngZone: NgZone,
policyApiService: PolicyApiServiceAbstraction,
policyService: InternalPolicyService,
passwordStrengthService: PasswordStrengthServiceAbstraction,
dialogService: DialogServiceAbstraction
dialogService: DialogService,
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
userVerificationService: UserVerificationService
) {
super(
router,
@@ -57,12 +59,13 @@ export class LockComponent extends BaseLockComponent {
stateService,
apiService,
logService,
keyConnectorService,
ngZone,
policyApiService,
policyService,
passwordStrengthService,
dialogService
dialogService,
deviceTrustCryptoService,
userVerificationService
);
}

View File

@@ -0,0 +1,105 @@
<div class="tw-container tw-mx-auto">
<div
class="tw-mx-auto tw-mt-5 tw-flex tw-max-w-lg tw-flex-col tw-items-center tw-justify-center tw-p-8"
>
<div class="tw-mb-6">
<img class="logo logo-themed" alt="Bitwarden" />
</div>
<ng-container *ngIf="loading">
<p class="text-center">
<i
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
</ng-container>
<div
*ngIf="!loading"
class="tw-w-full tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-6"
>
<ng-container *ngIf="data.state == State.ExistingUserUntrustedDevice">
<h2 bitTypography="h2" class="tw-mb-6">{{ "loginInitiated" | i18n }}</h2>
<p bitTypography="body1" class="tw-mb-6">
{{ "deviceApprovalRequired" | i18n }}
</p>
<form [formGroup]="rememberDeviceForm">
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="rememberDevice" />
<bit-label>{{ "rememberThisDevice" | i18n }} </bit-label>
<bit-hint bitTypography="body2">{{ "uncheckIfPublicDevice" | i18n }}</bit-hint>
</bit-form-control>
</form>
<div class="tw-mb-6 tw-flex tw-flex-col tw-space-y-3">
<button
*ngIf="data.showApproveFromOtherDeviceBtn"
(click)="approveFromOtherDevice()"
bitButton
type="button"
buttonType="primary"
block
>
{{ "approveFromYourOtherDevice" | i18n }}
</button>
<button
*ngIf="data.showReqAdminApprovalBtn"
(click)="requestAdminApproval()"
bitButton
type="button"
buttonType="secondary"
>
{{ "requestAdminApproval" | i18n }}
</button>
<button
*ngIf="data.showApproveWithMasterPasswordBtn"
(click)="approveWithMasterPassword()"
bitButton
type="button"
buttonType="secondary"
block
>
{{ "approveWithMasterPassword" | i18n }}
</button>
</div>
</ng-container>
<ng-container *ngIf="data.state == State.NewUser">
<h2 bitTypography="h2" class="tw-mb-6">{{ "loggedInExclamation" | i18n }}</h2>
<form [formGroup]="rememberDeviceForm">
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="rememberDevice" />
<bit-label>{{ "rememberThisDevice" | i18n }} </bit-label>
<bit-hint bitTypography="body2">{{ "uncheckIfPublicDevice" | i18n }}</bit-hint>
</bit-form-control>
</form>
<button
bitButton
type="button"
buttonType="primary"
block
class="tw-mb-6"
[bitAction]="createUserAction"
>
{{ "continue" | i18n }}
</button>
</ng-container>
<hr class="tw-mb-6 tw-mt-0" />
<div class="tw-m-0 tw-text-sm">
<p class="tw-mb-1">{{ "loggingInAs" | i18n }} {{ data.userEmail }}</p>
<a [routerLink]="[]" (click)="logOut()">{{ "notYou" | i18n }}</a>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
import { Component } from "@angular/core";
import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component";
@Component({
selector: "web-login-decryption-options",
templateUrl: "login-decryption-options.component.html",
})
export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent {
override async createUser(): Promise<void> {
try {
await super.createUser();
await this.router.navigate(["/vault"]);
} catch (error) {
this.validationService.showError(error);
}
}
createUserAction = async (): Promise<void> => {
return this.createUser();
};
}

View File

@@ -5,42 +5,71 @@
>
<div>
<img class="logo logo-themed" alt="Bitwarden" />
<p class="tw-mx-4 tw-mb-4 tw-mt-3 tw-text-center tw-text-xl">
{{ "loginOrCreateNewAccount" | i18n }}
</p>
<div
class="tw-mt-3 tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-6"
>
<h2 class="tw-mb-6 tw-text-xl tw-font-semibold">{{ "logInInitiated" | i18n }}</h2>
<ng-container *ngIf="state == StateEnum.StandardAuthRequest">
<p class="tw-mx-4 tw-mb-4 tw-mt-3 tw-text-center tw-text-xl">
{{ "loginOrCreateNewAccount" | i18n }}
</p>
<div class="tw-text-light">
<p class="tw-mb-6">{{ "notificationSentDevice" | i18n }}</p>
<div
class="tw-mt-3 tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-6"
>
<h2 class="tw-mb-6 tw-text-xl tw-font-semibold">{{ "loginInitiated" | i18n }}</h2>
<p class="tw-mb-6">
{{ "fingerprintMatchInfo" | i18n }}
</p>
<div class="tw-text-light">
<p class="tw-mb-6">{{ "notificationSentDevice" | i18n }}</p>
<p class="tw-mb-6">
{{ "fingerprintMatchInfo" | i18n }}
</p>
</div>
<div class="tw-mb-6">
<h4 class="tw-font-semibold">{{ "fingerprintPhraseHeader" | i18n }}</h4>
<p>
<code>{{ fingerprintPhrase }}</code>
</p>
</div>
<div class="tw-my-10" *ngIf="showResendNotification">
<a [routerLink]="[]" disabled="true" (click)="startPasswordlessLogin()">{{
"resendNotification" | i18n
}}</a>
</div>
<hr />
<div class="tw-text-light tw-mt-3">
{{ "loginWithDeviceEnabledNote" | i18n }}
<a routerLink="/login">{{ "viewAllLoginOptions" | i18n }}</a>
</div>
</div>
</ng-container>
<ng-container *ngIf="state == StateEnum.AdminAuthRequest">
<div
class="tw-mt-3 tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-6"
>
<h2 class="tw-mb-6 tw-text-xl tw-font-semibold">{{ "adminApprovalRequested" | i18n }}</h2>
<div class="tw-mb-6">
<h4 class="tw-font-semibold">{{ "fingerprintPhraseHeader" | i18n }}</h4>
<p>
<code>{{ fingerprintPhrase }}</code>
</p>
<div class="tw-text-light">
<p class="tw-mb-6">{{ "adminApprovalRequestSentToAdmins" | i18n }}</p>
<p class="tw-mb-6">{{ "youWillBeNotifiedOnceApproved" | i18n }}</p>
</div>
<div class="tw-mb-6">
<h4 class="tw-font-semibold">{{ "fingerprintPhraseHeader" | i18n }}</h4>
<p>
<code>{{ fingerprintPhrase }}</code>
</p>
</div>
<hr />
<div class="tw-text-light tw-mt-3">
{{ "troubleLoggingIn" | i18n }}
<a routerLink="/login-initiated">{{ "viewAllLoginOptions" | i18n }}</a>
</div>
</div>
<div class="tw-my-10" *ngIf="showResendNotification">
<a [routerLink]="[]" disabled="true" (click)="startPasswordlessLogin()">{{
"resendNotification" | i18n
}}</a>
</div>
<hr />
<div class="tw-text-light tw-mt-3">
{{ "loginWithDeviceEnabledNote" | i18n }}
<a routerLink="/login">{{ "viewAllLoginOptions" | i18n }}</a>
</div>
</div>
</ng-container>
</div>
</div>

View File

@@ -4,7 +4,9 @@ import { Router } from "@angular/router";
import { LoginWithDeviceComponent as BaseLoginWithDeviceComponent } from "@bitwarden/angular/auth/components/login-with-device.component";
import { AnonymousHubService } from "@bitwarden/common/abstractions/anonymousHub.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@@ -41,7 +43,9 @@ export class LoginWithDeviceComponent
anonymousHubService: AnonymousHubService,
validationService: ValidationService,
stateService: StateService,
loginService: LoginService
loginService: LoginService,
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
authReqCryptoService: AuthRequestCryptoServiceAbstraction
) {
super(
router,
@@ -58,7 +62,9 @@ export class LoginWithDeviceComponent
anonymousHubService,
validationService,
stateService,
loginService
loginService,
deviceTrustCryptoService,
authReqCryptoService
);
}
}

View File

@@ -6,7 +6,6 @@ import { first } from "rxjs/operators";
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
@@ -14,6 +13,7 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";

View File

@@ -4,12 +4,13 @@ import { CheckboxModule } from "@bitwarden/components";
import { SharedModule } from "../../../app/shared";
import { LoginDecryptionOptionsComponent } from "./login-decryption-options/login-decryption-options.component";
import { LoginWithDeviceComponent } from "./login-with-device.component";
import { LoginComponent } from "./login.component";
@NgModule({
imports: [SharedModule, CheckboxModule],
declarations: [LoginComponent, LoginWithDeviceComponent],
exports: [LoginComponent, LoginWithDeviceComponent],
declarations: [LoginComponent, LoginWithDeviceComponent, LoginDecryptionOptionsComponent],
exports: [LoginComponent, LoginWithDeviceComponent, LoginDecryptionOptionsComponent],
})
export class LoginModule {}

View File

@@ -35,7 +35,7 @@ export class RecoverTwoFactorComponent {
request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase();
request.email = this.email.trim().toLowerCase();
const key = await this.authService.makePreloginKey(this.masterPassword, request.email);
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
request.masterPasswordHash = await this.cryptoService.hashMasterKey(this.masterPassword, key);
this.formPromise = this.apiService.postTwoFactorRecover(request);
await this.formPromise;
this.platformUtilsService.showToast(

View File

@@ -123,8 +123,7 @@
</bit-label>
</div>
<hr />
<div class="tw-flex tw-space-x-2 tw-pt-2">
<div class="tw-space-x-2 tw-pt-2">
<ng-container *ngIf="!accountCreated">
<button
[block]="true"
@@ -135,10 +134,6 @@
>
{{ "createAccount" | i18n }}
</button>
<a bitButton [block]="true" buttonType="secondary" routerLink="/login">
<i class="bwi bwi-sign-in tw-mr-2"></i>
{{ "logIn" | i18n }}
</a>
</ng-container>
<ng-container *ngIf="accountCreated">
<button
@@ -152,6 +147,10 @@
</button>
</ng-container>
</div>
<p class="tw-m-0 tw-mt-5 tw-text-sm">
{{ "alreadyHaveAccount" | i18n }}
<a routerLink="/login">{{ "logIn" | i18n }}</a>
</p>
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary>
</div>
</form>

View File

@@ -4,7 +4,6 @@ import { Router } from "@angular/router";
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@@ -18,6 +17,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "app-register-form",
@@ -46,7 +46,7 @@ export class RegisterFormComponent extends BaseRegisterComponent {
environmentService: EnvironmentService,
logService: LogService,
auditService: AuditService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
formValidationErrorService,

View File

@@ -2,7 +2,6 @@ import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/components/set-password.component";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
@@ -15,6 +14,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "app-set-password",
@@ -36,7 +36,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
stateService: StateService,
organizationApiService: OrganizationApiServiceAbstraction,
organizationUserService: OrganizationUserService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
i18nService,

View File

@@ -89,12 +89,12 @@
<input
class="form-check-input"
type="checkbox"
id="rotateEncKey"
name="RotateEncKey"
[(ngModel)]="rotateEncKey"
(change)="rotateEncKeyClicked()"
id="rotateUserKey"
name="RotateUserKey"
[(ngModel)]="rotateUserKey"
(change)="rotateUserKeyClicked()"
/>
<label class="form-check-label" for="rotateEncKey">
<label class="form-check-label" for="rotateUserKey">
{{ "rotateAccountEncKey" | i18n }}
</label>
<a

View File

@@ -3,7 +3,6 @@ import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
@@ -11,7 +10,9 @@ import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/commo
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
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 { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
@@ -23,7 +24,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
@@ -32,13 +37,14 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "app-change-password",
templateUrl: "change-password.component.html",
})
export class ChangePasswordComponent extends BaseChangePasswordComponent {
rotateEncKey = false;
rotateUserKey = false;
currentMasterPassword: string;
masterPasswordHint: string;
checkForBreaches = true;
@@ -63,7 +69,9 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
private router: Router,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService,
dialogService: DialogServiceAbstraction
dialogService: DialogService,
private userVerificationService: UserVerificationService,
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction
) {
super(
i18nService,
@@ -78,7 +86,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
}
async ngOnInit() {
if (await this.keyConnectorService.getUsesKeyConnector()) {
if (!(await this.userVerificationService.hasMasterPassword())) {
this.router.navigate(["/settings/security/two-factor"]);
}
@@ -88,8 +96,8 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength);
}
async rotateEncKeyClicked() {
if (this.rotateEncKey) {
async rotateUserKeyClicked() {
if (this.rotateUserKey) {
const ciphers = await this.cipherService.getAllDecrypted();
let hasOldAttachments = false;
if (ciphers != null) {
@@ -107,7 +115,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
content: { key: "oldAttachmentsNeedFixDesc" },
acceptButtonText: { key: "learnMore" },
cancelButtonText: { key: "close" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (learnMore) {
@@ -115,7 +123,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
"https://bitwarden.com/help/attachments/#add-storage-space"
);
}
this.rotateEncKey = false;
this.rotateUserKey = false;
return;
}
@@ -127,18 +135,18 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
this.i18nService.t("updateEncryptionKeyExportWarning") +
" " +
this.i18nService.t("rotateEncKeyConfirmation"),
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!result) {
this.rotateEncKey = false;
this.rotateUserKey = false;
}
}
}
async submit() {
const hasEncKey = await this.cryptoService.hasEncKey();
if (!hasEncKey) {
const hasUserKey = await this.cryptoService.hasUserKey();
if (!hasUserKey) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("updateKey"));
return;
}
@@ -170,7 +178,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
return false;
}
if (this.rotateEncKey) {
if (this.rotateUserKey) {
await this.syncService.fullSync(true);
}
@@ -179,22 +187,23 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
async performSubmitActions(
newMasterPasswordHash: string,
newKey: SymmetricCryptoKey,
newEncKey: [SymmetricCryptoKey, EncString]
newMasterKey: MasterKey,
newUserKey: [UserKey, EncString]
) {
const masterKey = await this.cryptoService.getOrDeriveMasterKey(this.currentMasterPassword);
const request = new PasswordRequest();
request.masterPasswordHash = await this.cryptoService.hashPassword(
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
this.currentMasterPassword,
null
masterKey
);
request.masterPasswordHint = this.masterPasswordHint;
request.newMasterPasswordHash = newMasterPasswordHash;
request.key = newEncKey[1].encryptedString;
request.key = newUserKey[1].encryptedString;
try {
if (this.rotateEncKey) {
if (this.rotateUserKey) {
this.formPromise = this.apiService.postPassword(request).then(() => {
return this.updateKey(newKey, request.newMasterPasswordHash);
return this.updateKey(newMasterKey, request.newMasterPasswordHash);
});
} else {
this.formPromise = this.apiService.postPassword(request);
@@ -213,16 +222,16 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
}
}
private async updateKey(key: SymmetricCryptoKey, masterPasswordHash: string) {
const encKey = await this.cryptoService.makeEncKey(key);
const privateKey = await this.cryptoService.getPrivateKey();
private async updateKey(masterKey: MasterKey, masterPasswordHash: string) {
const [newUserKey, masterKeyEncUserKey] = await this.cryptoService.makeUserKey(masterKey);
const userPrivateKey = await this.cryptoService.getPrivateKey();
let encPrivateKey: EncString = null;
if (privateKey != null) {
encPrivateKey = await this.cryptoService.encrypt(privateKey, encKey[0]);
if (userPrivateKey != null) {
encPrivateKey = await this.cryptoService.encrypt(userPrivateKey, newUserKey);
}
const request = new UpdateKeyRequest();
request.privateKey = encPrivateKey != null ? encPrivateKey.encryptedString : null;
request.key = encKey[1].encryptedString;
request.key = masterKeyEncUserKey.encryptedString;
request.masterPasswordHash = masterPasswordHash;
const folders = await firstValueFrom(this.folderService.folderViews$);
@@ -230,7 +239,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
if (folders[i].id == null) {
continue;
}
const folder = await this.folderService.encrypt(folders[i], encKey[0]);
const folder = await this.folderService.encrypt(folders[i], newUserKey);
request.folders.push(new FolderWithIdRequest(folder));
}
@@ -240,24 +249,26 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
continue;
}
const cipher = await this.cipherService.encrypt(ciphers[i], encKey[0]);
const cipher = await this.cipherService.encrypt(ciphers[i], newUserKey);
request.ciphers.push(new CipherWithIdRequest(cipher));
}
const sends = await firstValueFrom(this.sendService.sends$);
await Promise.all(
sends.map(async (send) => {
const cryptoKey = await this.cryptoService.decryptToBytes(send.key, null);
send.key = (await this.cryptoService.encrypt(cryptoKey, encKey[0])) ?? send.key;
const sendKey = await this.cryptoService.decryptToBytes(send.key, null);
send.key = (await this.cryptoService.encrypt(sendKey, newUserKey)) ?? send.key;
request.sends.push(new SendWithIdRequest(send));
})
);
await this.deviceTrustCryptoService.rotateDevicesTrust(newUserKey, masterPasswordHash);
await this.apiService.postAccountKey(request);
await this.updateEmergencyAccesses(encKey[0]);
await this.updateEmergencyAccesses(newUserKey);
await this.updateAllResetPasswordKeys(encKey[0], masterPasswordHash);
await this.updateAllResetPasswordKeys(newUserKey, masterPasswordHash);
}
private async updateEmergencyAccesses(encKey: SymmetricCryptoKey) {
@@ -274,7 +285,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
const updateRequest = new EmergencyAccessUpdateRequest();
updateRequest.type = details.type;
@@ -285,7 +296,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
}
}
private async updateAllResetPasswordKeys(encKey: SymmetricCryptoKey, masterPasswordHash: string) {
private async updateAllResetPasswordKeys(userKey: UserKey, masterPasswordHash: string) {
const orgs = await this.organizationService.getAll();
for (const org of orgs) {
@@ -299,7 +310,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
const publicKey = Utils.fromB64ToArray(response?.publicKey);
// Re-enroll - encrypt user's encKey.key with organization public key
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey);
// Create/Execute request
const request = new OrganizationUserResetPasswordEnrollmentRequest();

View File

@@ -1,6 +1,5 @@
import { Component } from "@angular/core";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -11,6 +10,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "emergency-access-attachments",
@@ -29,7 +29,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen
apiService: ApiService,
logService: LogService,
fileDownloadService: FileDownloadService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
cipherService,

View File

@@ -33,7 +33,7 @@ export class EmergencyAccessConfirmComponent implements OnInit {
const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId);
if (publicKeyResponse != null) {
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const fingerprint = await this.cryptoService.getFingerprint(this.userId, publicKey.buffer);
const fingerprint = await this.cryptoService.getFingerprint(this.userId, publicKey);
if (fingerprint != null) {
this.fingerprint = fingerprint.join("-");
}

View File

@@ -2,7 +2,6 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angu
import { takeUntil } from "rxjs";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
@@ -17,8 +16,12 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "emergency-access-takeover",
@@ -48,7 +51,7 @@ export class EmergencyAccessTakeoverComponent
policyService: PolicyService,
private apiService: ApiService,
private logService: LogService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
i18nService,
@@ -91,9 +94,9 @@ export class EmergencyAccessTakeoverComponent
);
const oldKeyBuffer = await this.cryptoService.rsaDecrypt(takeoverResponse.keyEncrypted);
const oldEncKey = new SymmetricCryptoKey(oldKeyBuffer);
const oldUserKey = new SymmetricCryptoKey(oldKeyBuffer) as UserKey;
if (oldEncKey == null) {
if (oldUserKey == null) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
@@ -102,7 +105,7 @@ export class EmergencyAccessTakeoverComponent
return;
}
const key = await this.cryptoService.makeKey(
const masterKey = await this.cryptoService.makeMasterKey(
this.masterPassword,
this.email,
takeoverResponse.kdf,
@@ -112,12 +115,12 @@ export class EmergencyAccessTakeoverComponent
takeoverResponse.kdfParallelism
)
);
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
const masterKeyHash = await this.cryptoService.hashMasterKey(this.masterPassword, masterKey);
const encKey = await this.cryptoService.remakeEncKey(key, oldEncKey);
const encKey = await this.cryptoService.encryptUserKeyWithMasterKey(masterKey, oldUserKey);
const request = new EmergencyAccessPasswordRequest();
request.newMasterPasswordHash = masterPasswordHash;
request.newMasterPasswordHash = masterKeyHash;
request.key = encKey[1].encryptedString;
this.apiService.postEmergencyAccessPassword(this.emergencyAccessId, request);

View File

@@ -5,7 +5,10 @@ import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EmergencyAccessViewResponse } from "@bitwarden/common/auth/models/response/emergency-access.response";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
@@ -87,13 +90,13 @@ export class EmergencyAccessViewComponent implements OnInit {
const decCiphers: CipherView[] = [];
const oldKeyBuffer = await this.cryptoService.rsaDecrypt(response.keyEncrypted);
const oldEncKey = new SymmetricCryptoKey(oldKeyBuffer);
const oldUserKey = new SymmetricCryptoKey(oldKeyBuffer) as UserKey;
const promises: any[] = [];
ciphers.forEach((cipherResponse) => {
const cipherData = new CipherData(cipherResponse);
const cipher = new Cipher(cipherData);
promises.push(cipher.decrypt(oldEncKey).then((c) => decCiphers.push(c)));
promises.push(cipher.decrypt(oldUserKey).then((c) => decCiphers.push(c)));
});
await Promise.all(promises);

View File

@@ -1,7 +1,6 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@@ -19,6 +18,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components";
import { EmergencyAccessAddEditComponent } from "./emergency-access-add-edit.component";
import { EmergencyAccessConfirmComponent } from "./emergency-access-confirm.component";
@@ -56,7 +56,7 @@ export class EmergencyAccessComponent implements OnInit {
private logService: LogService,
private stateService: StateService,
private organizationService: OrganizationService,
protected dialogService: DialogServiceAbstraction
protected dialogService: DialogService
) {}
async ngOnInit() {
@@ -174,7 +174,7 @@ export class EmergencyAccessComponent implements OnInit {
const confirmed = await this.dialogService.openSimpleDialog({
title: this.userNamePipe.transform(details),
content: { key: "removeUserConfirmation" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -207,7 +207,7 @@ export class EmergencyAccessComponent implements OnInit {
placeholders: [details.waitTimeDays.toString()],
},
acceptButtonText: { key: "requestAccess" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -236,7 +236,7 @@ export class EmergencyAccessComponent implements OnInit {
placeholders: [this.userNamePipe.transform(details), type],
},
acceptButtonText: { key: "approve" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -300,22 +300,25 @@ export class EmergencyAccessComponent implements OnInit {
}
}
// Encrypt the master password hash using the grantees public key, and send it to bitwarden for escrow.
// Encrypt the user key with the grantees public key, and send it to bitwarden for escrow.
private async doConfirmation(details: EmergencyAccessGranteeDetailsResponse) {
const encKey = await this.cryptoService.getEncKey();
const userKey = await this.cryptoService.getUserKey();
if (!userKey) {
throw new Error("No user key found");
}
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
try {
this.logService.debug(
"User's fingerprint: " +
(await this.cryptoService.getFingerprint(details.granteeId, publicKey.buffer)).join("-")
(await this.cryptoService.getFingerprint(details.granteeId, publicKey)).join("-")
);
} catch {
// Ignore errors since it's just a debug message
}
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey);
const request = new EmergencyAccessConfirmRequest();
request.key = encryptedKey.encryptedString;
await this.apiService.postEmergencyAccessConfirm(details.id, request);

View File

@@ -1,6 +1,5 @@
import { Component } from "@angular/core";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
@@ -18,6 +17,7 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { DialogService } from "@bitwarden/components";
import { AddEditComponent as BaseAddEditComponent } from "../../../vault/individual-vault/add-edit.component";
@@ -47,7 +47,7 @@ export class EmergencyAddEditComponent extends BaseAddEditComponent {
organizationService: OrganizationService,
logService: LogService,
sendApiService: SendApiService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
cipherService,

View File

@@ -1,6 +1,5 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@@ -12,6 +11,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components";
import { TwoFactorBaseComponent } from "./two-factor-base.component";
@@ -52,7 +52,7 @@ export class TwoFactorAuthenticatorComponent
platformUtilsService: PlatformUtilsService,
logService: LogService,
private stateService: StateService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
apiService,

View File

@@ -1,6 +1,5 @@
import { Directive, EventEmitter, Output } from "@angular/core";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@@ -11,6 +10,7 @@ import { AuthResponseBase } from "@bitwarden/common/auth/types/auth-response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
@Directive()
export abstract class TwoFactorBaseComponent {
@@ -32,7 +32,7 @@ export abstract class TwoFactorBaseComponent {
protected platformUtilsService: PlatformUtilsService,
protected logService: LogService,
protected userVerificationService: UserVerificationService,
protected dialogService: DialogServiceAbstraction
protected dialogService: DialogService
) {}
protected auth(authResponse: AuthResponseBase) {
@@ -54,7 +54,7 @@ export abstract class TwoFactorBaseComponent {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "disable" },
content: { key: "twoStepDisableDesc" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {

View File

@@ -1,6 +1,5 @@
import { Component } from "@angular/core";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@@ -10,6 +9,7 @@ import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
import { TwoFactorBaseComponent } from "./two-factor-base.component";
@@ -32,7 +32,7 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
platformUtilsService: PlatformUtilsService,
logService: LogService,
userVerificationService: UserVerificationService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
apiService,

View File

@@ -1,6 +1,5 @@
import { Component } from "@angular/core";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@@ -12,6 +11,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DialogService } from "@bitwarden/components";
import { TwoFactorBaseComponent } from "./two-factor-base.component";
@@ -36,7 +36,7 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
logService: LogService,
userVerificationService: UserVerificationService,
private stateService: StateService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
apiService,

View File

@@ -1,6 +1,5 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-body">
<p>{{ "twoStepLoginAuthDesc" | i18n }}</p>
<app-user-verification [(ngModel)]="secret" ngDefaultControl name="secret">
</app-user-verification>
</div>

View File

@@ -1,6 +1,5 @@
import { Component, NgZone } from "@angular/core";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@@ -15,6 +14,7 @@ import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
import { TwoFactorBaseComponent } from "./two-factor-base.component";
@@ -51,7 +51,7 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
private ngZone: NgZone,
logService: LogService,
userVerificationService: UserVerificationService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
apiService,
@@ -98,7 +98,7 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
const confirmed = await this.dialogService.openSimpleDialog({
title: name,
content: { key: "removeU2fConfirmation" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {

View File

@@ -1,6 +1,5 @@
import { Component } from "@angular/core";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@@ -10,6 +9,7 @@ import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
import { TwoFactorBaseComponent } from "./two-factor-base.component";
@@ -38,7 +38,7 @@ export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent {
platformUtilsService: PlatformUtilsService,
logService: LogService,
userVerificationService: UserVerificationService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
apiService,

View File

@@ -3,11 +3,11 @@ import { Component, Inject } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { UserVerificationPromptComponent as BaseUserVerificationPrompt } from "@bitwarden/angular/auth/components/user-verification-prompt.component";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
export interface UserVerificationPromptParams {
confirmDescription: string;
@@ -50,7 +50,7 @@ export class UserVerificationPromptComponent extends BaseUserVerificationPrompt
* @param config Configuration for the dialog
*/
export const openUserVerificationPrompt = (
dialogService: DialogServiceAbstraction,
dialogService: DialogService,
config: DialogConfig<UserVerificationPromptParams>
) => {
return dialogService.open<boolean, UserVerificationPromptParams>(

View File

@@ -1,4 +1,4 @@
<ng-container *ngIf="!usesKeyConnector">
<ng-container *ngIf="hasMasterPassword">
<bit-form-field disableMargin>
<bit-label>{{ "masterPass" | i18n }}</bit-label>
<input
@@ -14,7 +14,7 @@
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
</bit-form-field>
</ng-container>
<ng-container *ngIf="usesKeyConnector">
<ng-container *ngIf="!hasMasterPassword">
<div class="tw-mb-6">
<label class="tw-block">{{ "sendVerificationCode" | i18n }}</label>
<button type="button" bitButton buttonType="secondary" [bitAction]="requestOTP" appAutofocus>

View File

@@ -10,6 +10,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { HttpStatusCode } from "@bitwarden/common/enums";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -39,7 +40,8 @@ export class SsoComponent extends BaseSsoComponent {
logService: LogService,
private orgDomainApiService: OrgDomainApiServiceAbstraction,
private loginService: LoginService,
private validationService: ValidationService
private validationService: ValidationService,
configService: ConfigServiceAbstraction
) {
super(
authService,
@@ -52,7 +54,8 @@ export class SsoComponent extends BaseSsoComponent {
cryptoFunctionService,
environmentService,
passwordGenerationService,
logService
logService,
configService
);
this.redirectUri = window.location.origin + "/sso-connector.html";
this.clientId = "web";

View File

@@ -58,13 +58,13 @@
</div>
<div class="tw-pt-44" *ngIf="useTrialStepper">
<div class="tw-rounded tw-border tw-border-solid tw-border-secondary-300 tw-bg-background">
<div class="tw-flex tw-h-auto tw-w-full tw-rounded-t tw-bg-secondary-100">
<div class="tw-flex tw-h-auto tw-w-full tw-gap-5 tw-rounded-t tw-bg-secondary-100">
<h2 class="tw-pb-4 tw-pl-4 tw-pt-5 tw-text-base tw-font-bold tw-uppercase">
{{ "startYour7DayFreeTrialOfBitwardenFor" | i18n : org }}
</h2>
<environment-selector
[hasFlags]="true"
class="tw-mr-4 tw-mt-6 tw-text-end"
class="tw-mr-4 tw-mt-6 tw-flex-shrink-0 tw-text-end"
></environment-selector>
</div>
<app-vertical-stepper #stepper linear (selectionChange)="stepSelectionChange($event)">

View File

@@ -94,6 +94,13 @@ export class TrialInitiationComponent implements OnInit, OnDestroy {
if (this.referenceData.id === "") {
this.referenceData.id = null;
} else {
// Matches "_ga_QBRN562QQQ=value1.value2.session" and captures values and session.
const regex = /_ga_QBRN562QQQ=([^.]+)\.([^.]+)\.(\d+)/;
const match = document.cookie.match(regex);
if (match) {
this.referenceData.session = match[3];
}
}
}

View File

@@ -1,7 +1,8 @@
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import { Component, Inject, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
@@ -9,6 +10,7 @@ import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -42,7 +44,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
twoFactorService: TwoFactorService,
appIdService: AppIdService,
private routerService: RouterService,
loginService: LoginService
loginService: LoginService,
configService: ConfigServiceAbstraction,
@Inject(WINDOW) protected win: Window
) {
super(
authService,
@@ -50,14 +54,15 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
i18nService,
apiService,
platformUtilsService,
window,
win,
environmentService,
stateService,
route,
logService,
twoFactorService,
appIdService,
loginService
loginService,
configService
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
@@ -81,7 +86,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
);
}
async goAfterLogIn() {
goAfterLogIn = async () => {
this.loginService.clearValues();
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl) {
@@ -103,9 +108,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
this.router.navigate([this.successRoute], {
queryParams: {
identifier: this.identifier,
identifier: this.orgIdentifier,
},
});
}
}
};
}

View File

@@ -2,7 +2,6 @@ import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
@@ -13,6 +12,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "app-update-password",
@@ -31,7 +31,7 @@ export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
logService: LogService,
stateService: StateService,
userVerificationService: UserVerificationService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
router,

View File

@@ -87,13 +87,32 @@
<td bitCell>{{ "passwordManager" | i18n }} - {{ "freeOrganization" | i18n }}</td>
<td bitCell class="tw-text-right">{{ "free" | i18n }}</td>
</tr>
<tr bitRow *ngIf="userOrg.useSecretsManager">
<tr bitRow *ngIf="userOrg.useSecretsManager && !sub.secretsManagerBeta">
<td bitCell>{{ "secretsManager" | i18n }} - {{ "freeOrganization" | i18n }}</td>
<td bitCell class="tw-text-right">{{ "free" | i18n }}</td>
</tr>
</ng-container>
<tr bitRow *ngIf="sub.secretsManagerBeta">
<td bitCell>
{{ "secretsManager" | i18n }} -
{{ "beta" | i18n }}
({{ "annually" | i18n }}) @
{{ 0 | currency : "$" }}
<span bitBadge badgeType="warning" class="tw-ml-2">{{
"betaEnding" | i18n | uppercase
}}</span>
</td>
<td bitCell class="tw-text-right">{{ 0 | currency : "$" }} /{{ "year" | i18n }}</td>
</tr>
</ng-template>
</bit-table>
<bit-callout
*ngIf="sub.secretsManagerBeta && !userOrg.isFreeOrg"
type="warning"
class="tw-mt-4 tw-block"
>
{{ smBetaEndedDesc }}
</bit-callout>
</div>
</ng-container>

View File

@@ -1,8 +1,8 @@
import { DatePipe } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ModalConfig, ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
@@ -17,6 +17,8 @@ import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstraction
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components";
import {
BillingSyncApiKeyComponent,
@@ -45,6 +47,9 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
firstLoaded = false;
loading: boolean;
private readonly _smBetaEndingDate = new Date(2023, 7, 15);
private readonly _smGracePeriodEndingDate = new Date(2023, 10, 14);
private destroy$ = new Subject<void>();
constructor(
@@ -56,8 +61,9 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
private organizationService: OrganizationService,
private organizationApiService: OrganizationApiServiceAbstraction,
private route: ActivatedRoute,
private dialogService: DialogServiceAbstraction,
private configService: ConfigServiceAbstraction
private dialogService: DialogService,
private configService: ConfigServiceAbstraction,
private datePipe: DatePipe
) {}
async ngOnInit() {
@@ -122,6 +128,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
this.userOrg.useSecretsManager &&
this.subscription != null &&
this.sub.secretsManagerPlan?.hasAdditionalSeatsOption &&
!this.sub.secretsManagerBeta &&
!this.subscription.cancelled &&
!this.subscriptionMarkedForCancel;
@@ -256,6 +263,14 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
);
}
get smBetaEndedDesc() {
return this.i18nService.translate(
"smBetaEndedDesc",
this.datePipe.transform(this._smBetaEndingDate),
Utils.daysRemaining(this._smGracePeriodEndingDate).toString()
);
}
cancel = async () => {
if (this.loading) {
return;
@@ -264,7 +279,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "cancelSubscription" },
content: { key: "cancelConfirmation" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -292,7 +307,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "reinstateSubscription" },
content: { key: "reinstateConfirmation" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -364,7 +379,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
title: { key: "removeSponsorship" },
content: { key: "removeSponsorshipConfirmation" },
acceptButtonText: { key: "remove" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {

View File

@@ -5,7 +5,7 @@
<bit-hint>
<strong>{{ "total" | i18n }}:</strong>
{{ formGroup.value.seatCount || 0 }} &times; {{ options.seatPrice | currency : "$" }} =
{{ seatTotal | currency : "$" }} / {{ options.interval | i18n }}
{{ seatTotalCost | currency : "$" }} / {{ options.interval | i18n }}
</bit-hint>
</bit-form-field>
<bit-form-control>
@@ -28,7 +28,7 @@
<bit-hint>
<strong>{{ "maxSeatCost" | i18n }}:</strong>
{{ formGroup.value.maxAutoscaleSeats || 0 }} &times;
{{ options.seatPrice | currency : "$" }} = {{ maxSeatTotal | currency : "$" }} /
{{ options.seatPrice | currency : "$" }} = {{ maxSeatTotalCost | currency : "$" }} /
{{ options.interval | i18n }}
</bit-hint>
</bit-form-field>
@@ -44,16 +44,14 @@
/>
<bit-hint>
<div>
{{
"additionalServiceAccountsDesc"
| i18n : options.baseServiceAccountCount : (monthlyServiceAccountPrice | currency : "$")
}}
{{ "includedServiceAccounts" | i18n : options.baseServiceAccountCount }}
{{ "addAdditionalServiceAccounts" | i18n : (monthlyServiceAccountPrice | currency : "$") }}
</div>
<div>
<strong>{{ "total" | i18n }}:</strong>
{{ formGroup.value.additionalServiceAccounts || 0 }} &times;
{{ options.additionalServiceAccountPrice | currency : "$" }} =
{{ serviceAccountTotal | currency : "$" }} / {{ options.interval | i18n }}
{{ serviceAccountTotalCost | currency : "$" }} / {{ options.interval | i18n }}
</div>
</bit-hint>
</bit-form-field>
@@ -80,10 +78,13 @@
[min]="formGroup.value.additionalServiceAccounts"
/>
<bit-hint>
<div>
{{ "includedServiceAccounts" | i18n : options.baseServiceAccountCount }}
</div>
<strong>{{ "maxServiceAccountCost" | i18n }}:</strong>
{{ formGroup.value.maxAutoscaleServiceAccounts || 0 }} &times;
{{ maxAdditionalServiceAccounts }} &times;
{{ options.additionalServiceAccountPrice | currency : "$" }} =
{{ maxServiceAccountTotal | currency : "$" }} / {{ options.interval | i18n }}
{{ maxServiceAccountTotalCost | currency : "$" }} / {{ options.interval | i18n }}
</bit-hint>
</bit-form-field>
<button type="submit" bitButton buttonType="primary" bitFormButton>

View File

@@ -72,24 +72,26 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest
: this.options.additionalServiceAccountPrice / 12;
}
get serviceAccountTotal(): number {
get serviceAccountTotalCost(): number {
return Math.abs(
this.formGroup.value.additionalServiceAccounts * this.options.additionalServiceAccountPrice
);
}
get seatTotal(): number {
get seatTotalCost(): number {
return Math.abs(this.formGroup.value.seatCount * this.options.seatPrice);
}
get maxServiceAccountTotal(): number {
return Math.abs(
(this.formGroup.value.maxAutoscaleServiceAccounts ?? 0) *
this.options.additionalServiceAccountPrice
);
get maxAdditionalServiceAccounts(): number {
const maxTotalServiceAccounts = this.formGroup.value.maxAutoscaleServiceAccounts ?? 0;
return Math.max(0, maxTotalServiceAccounts - this.options.baseServiceAccountCount);
}
get maxSeatTotal(): number {
get maxServiceAccountTotalCost(): number {
return this.maxAdditionalServiceAccounts * this.options.additionalServiceAccountPrice;
}
get maxSeatTotalCost(): number {
return Math.abs((this.formGroup.value.maxAutoscaleSeats ?? 0) * this.options.seatPrice);
}
@@ -115,7 +117,7 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest
if (value.limitServiceAccounts) {
maxAutoscaleServiceAccountsControl.setValidators([
Validators.min(value.additionalServiceAccounts),
Validators.min(value.additionalServiceAccounts + this.options.baseServiceAccountCount),
]);
maxAutoscaleServiceAccountsControl.enable({ emitEvent: false });
} else {

View File

@@ -2,6 +2,8 @@ import { Component, EventEmitter, Input, Output } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { SecretsManagerSubscribeRequest } from "@bitwarden/common/billing/models/request/sm-subscribe.request";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
@@ -25,7 +27,8 @@ export class SecretsManagerSubscribeStandaloneComponent {
private formBuilder: FormBuilder,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private organizationApiService: OrganizationApiServiceAbstraction
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationService: InternalOrganizationServiceAbstraction
) {}
submit = async () => {
@@ -37,7 +40,15 @@ export class SecretsManagerSubscribeStandaloneComponent {
? this.formGroup.value.additionalServiceAccounts
: 0;
await this.organizationApiService.subscribeToSecretsManager(this.organization.id, request);
const profileOrganization = await this.organizationApiService.subscribeToSecretsManager(
this.organization.id,
request
);
const organizationData = new OrganizationData(profileOrganization, {
isMember: this.organization.isMember,
isProviderUser: this.organization.isProviderUser,
});
await this.organizationService.upsert(organizationData);
this.platformUtilsService.showToast("success", null, this.i18nService.t("subscriptionUpdated"));

View File

@@ -56,10 +56,13 @@
<bit-form-field>
<bit-label>{{ "additionalServiceAccounts" | i18n }}</bit-label>
<input bitInput formControlName="additionalServiceAccounts" type="number" />
<bit-hint>{{
"additionalServiceAccountsDesc"
| i18n : serviceAccountsIncluded : (monthlyCostPerServiceAccount | currency : "$")
}}</bit-hint>
<bit-hint>
{{ "includedServiceAccounts" | i18n : serviceAccountsIncluded }}
{{
"addAdditionalServiceAccounts"
| i18n : (monthlyCostPerServiceAccount | currency : "$")
}}
</bit-hint>
</bit-form-field>
</div>

View File

@@ -12,6 +12,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/bit-pay-invoice.request";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { PayPalConfig } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -43,13 +44,15 @@ export class AddCreditComponent implements OnInit {
private userId: string;
private name: string;
private email: string;
private region: string;
constructor(
private stateService: StateService,
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private organizationService: OrganizationService,
private logService: LogService
private logService: LogService,
private configService: ConfigServiceAbstraction
) {
const payPalConfig = process.env.PAYPAL_CONFIG as PayPalConfig;
this.ppButtonFormAction = payPalConfig.buttonAction;
@@ -76,7 +79,9 @@ export class AddCreditComponent implements OnInit {
this.email = this.subject;
this.ppButtonCustomField = "user_id:" + this.userId;
}
this.region = await this.configService.getCloudRegion();
this.ppButtonCustomField += ",account_credit:1";
this.ppButtonCustomField += `,region:${this.region}`;
this.returnUrl = window.location.href;
}

View File

@@ -329,7 +329,7 @@
>
{{ "submit" | i18n }}
</button>
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" *ngIf="showCancel">
<button type="button" buttonType="secondary" bitButton (click)="cancel()" *ngIf="showCancel">
{{ "cancel" | i18n }}
</button>
</div>

View File

@@ -31,7 +31,10 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
OrgKey,
SymmetricCryptoKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { secretsManagerSubscribeFormFactory } from "../organizations/secrets-manager/sm-subscribe.component";
@@ -314,16 +317,11 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
return 0;
}
let subTotal = plan.basePrice;
if (plan.hasAdditionalSeatsOption && formValues.userSeats) {
subTotal += this.seatTotal(plan, formValues.userSeats);
}
if (plan.hasAdditionalStorageOption && formValues.additionalServiceAccounts) {
subTotal += this.additionalServiceAccountTotal(this.selectedPlan);
}
return subTotal;
return (
plan.basePrice +
this.seatTotal(plan, formValues.userSeats) +
this.additionalServiceAccountTotal(plan)
);
}
get freeTrial() {
@@ -420,19 +418,19 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
const doSubmit = async (): Promise<string> => {
let orgId: string = null;
if (this.createOrganization) {
const shareKey = await this.cryptoService.makeShareKey();
const key = shareKey[0].encryptedString;
const orgKey = await this.cryptoService.makeOrgKey<OrgKey>();
const key = orgKey[0].encryptedString;
const collection = await this.cryptoService.encrypt(
this.i18nService.t("defaultCollection"),
shareKey[1]
orgKey[1]
);
const collectionCt = collection.encryptedString;
const orgKeys = await this.cryptoService.makeKeyPair(shareKey[1]);
const orgKeys = await this.cryptoService.makeKeyPair(orgKey[1]);
if (this.selfHosted) {
orgId = await this.createSelfHosted(key, collectionCt, orgKeys);
} else {
orgId = await this.createCloudHosted(key, collectionCt, orgKeys, shareKey[1]);
orgId = await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]);
}
this.platformUtilsService.showToast(

View File

@@ -112,6 +112,7 @@
*ngIf="showAdjustPayment"
>
</app-adjust-payment>
<p *ngIf="isUnpaid">{{ "paymentChargedWithUnpaidSubscription" | i18n }}</p>
<ng-container *ngIf="forOrganization">
<h2 class="spaced-header">{{ "taxInformation" | i18n }}</h2>
<p>{{ "taxInformationDesc" | i18n }}</p>

View File

@@ -2,16 +2,17 @@ import { Component, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, FormControl, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response";
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { BillingPaymentResponse } from "@bitwarden/common/billing/models/response/billing-payment.response";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response";
import { VerifyBankRequest } from "@bitwarden/common/models/request/verify-bank.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
import { TaxInfoComponent } from "./tax-info.component";
@@ -28,9 +29,11 @@ export class PaymentMethodComponent implements OnInit {
showAdjustPayment = false;
showAddCredit = false;
billing: BillingPaymentResponse;
org: OrganizationResponse;
org: OrganizationSubscriptionResponse;
sub: SubscriptionResponse;
paymentMethodType = PaymentMethodType;
organizationId: string;
isUnpaid = false;
verifyBankPromise: Promise<any>;
taxFormPromise: Promise<any>;
@@ -57,7 +60,7 @@ export class PaymentMethodComponent implements OnInit {
private logService: LogService,
private route: ActivatedRoute,
private formBuilder: FormBuilder,
private dialogService: DialogServiceAbstraction
private dialogService: DialogService
) {}
async ngOnInit() {
@@ -83,13 +86,23 @@ export class PaymentMethodComponent implements OnInit {
if (this.forOrganization) {
const billingPromise = this.organizationApiService.getBilling(this.organizationId);
const orgPromise = this.organizationApiService.get(this.organizationId);
const organizationSubscriptionPromise = this.organizationApiService.getSubscription(
this.organizationId
);
[this.billing, this.org] = await Promise.all([billingPromise, orgPromise]);
[this.billing, this.org] = await Promise.all([
billingPromise,
organizationSubscriptionPromise,
]);
} else {
this.billing = await this.apiService.getUserBillingPayment();
const billingPromise = this.apiService.getUserBillingPayment();
const subPromise = this.apiService.getUserSubscription();
[this.billing, this.sub] = await Promise.all([billingPromise, subPromise]);
}
this.isUnpaid = this.subscription?.status === "unpaid" ?? false;
this.loading = false;
}
@@ -100,7 +113,7 @@ export class PaymentMethodComponent implements OnInit {
content: { key: "cannotPerformInAppPurchase" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: SimpleDialogType.WARNING,
type: "warning",
});
return;
@@ -122,11 +135,12 @@ export class PaymentMethodComponent implements OnInit {
content: { key: "cannotPerformInAppPurchase" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: SimpleDialogType.WARNING,
type: "warning",
});
return;
}
this.showAdjustPayment = true;
}
@@ -214,4 +228,8 @@ export class PaymentMethodComponent implements OnInit {
this.paymentSource.type === PaymentMethodType.GoogleInApp)
);
}
get subscription() {
return this.sub?.subscription ?? this.org?.subscription ?? null;
}
}

View File

@@ -4,7 +4,7 @@ import { RouterModule, Routes } from "@angular/router";
import { BillingHistoryViewComponent } from "../../billing/settings/billing-history-view.component";
import { PaymentMethodComponent } from "../../billing/settings/payment-method.component";
import { UserSubscriptionComponent } from "../../billing/settings/user-subscription.component";
import { PremiumComponent } from "../../settings/premium.component";
import { PremiumComponent } from "../../vault/settings/premium.component";
import { SubscriptionComponent } from "./subscription.component";

View File

@@ -1,7 +1,6 @@
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@@ -10,6 +9,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "app-user-subscription",
@@ -36,7 +36,7 @@ export class UserSubscriptionComponent implements OnInit {
private router: Router,
private logService: LogService,
private fileDownloadService: FileDownloadService,
private dialogService: DialogServiceAbstraction,
private dialogService: DialogService,
private environmentService: EnvironmentService
) {
this.selfHosted = platformUtilsService.isSelfHost();
@@ -75,7 +75,7 @@ export class UserSubscriptionComponent implements OnInit {
content: { key: "manageSubscriptionFromStore" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: SimpleDialogType.WARNING,
type: "warning",
});
return;
@@ -84,7 +84,7 @@ export class UserSubscriptionComponent implements OnInit {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "reinstateSubscription" },
content: { key: "reinstateConfirmation" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -112,7 +112,7 @@ export class UserSubscriptionComponent implements OnInit {
content: { key: "manageSubscriptionFromStore" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: SimpleDialogType.WARNING,
type: "warning",
});
return;
@@ -121,7 +121,7 @@ export class UserSubscriptionComponent implements OnInit {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "cancelSubscription" },
content: { key: "cancelConfirmation" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -175,7 +175,7 @@ export class UserSubscriptionComponent implements OnInit {
content: { key: "cannotPerformInAppPurchase" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: SimpleDialogType.WARNING,
type: "warning",
});
return;

View File

@@ -2,7 +2,6 @@ import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
@@ -21,6 +20,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components";
import { OrganizationUserView } from "../admin-console/organizations/core/views/organization-user.view";
import { UserConfirmComponent } from "../admin-console/organizations/manage/user-confirm.component";
@@ -110,7 +110,7 @@ export abstract class BasePeopleComponent<
private searchPipe: SearchPipe,
protected userNamePipe: UserNamePipe,
protected stateService: StateService,
protected dialogService: DialogServiceAbstraction
protected dialogService: DialogService
) {}
abstract edit(user: UserType): void;
@@ -222,7 +222,7 @@ export abstract class BasePeopleComponent<
return this.dialogService.openSimpleDialog({
title: this.userNamePipe.transform(user),
content: { key: "removeUserConfirmation" },
type: SimpleDialogType.WARNING,
type: "warning",
});
}
@@ -252,7 +252,7 @@ export abstract class BasePeopleComponent<
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
content: this.revokeWarningMessage(),
acceptButtonText: { key: "revokeAccess" },
type: SimpleDialogType.WARNING,
type: "warning",
});
}
@@ -374,7 +374,7 @@ export abstract class BasePeopleComponent<
}
try {
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey);
this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
} catch (e) {
this.logService.error(e);

View File

@@ -10,8 +10,12 @@
aria-hidden="true"
[style.visibility]="isUsServer ? 'visible' : 'hidden'"
></i>
<img src="../../images/us_flag.png" alt="{{ 'usFlag' | i18n }}" class="pb-1 mr-1" />
{{ "us" | i18n }}
<img
src="../../images/flag-us.svg"
alt="{{ 'usFlag' | i18n }}"
class="tw-mr-1 tw-w-6 tw-pb-1"
/>
{{ "usDomain" | i18n }}
</a>
<a
bitMenuItem
@@ -24,31 +28,35 @@
aria-hidden="true"
[style.visibility]="isEuServer ? 'visible' : 'hidden'"
></i>
<img src="../../images/eu_flag.png" alt="{{ 'euFlag' | i18n }}" class="pb-1 mr-1" />
{{ "eu" | i18n }}
<img
src="../../images/flag-eu.svg"
alt="{{ 'euFlag' | i18n }}"
class="tw-mr-1 tw-w-6 tw-pb-1"
/>
{{ "euDomain" | i18n }}
</a>
</bit-menu>
<span *ngIf="hasFlags" class="!tw-inline-block !tw-w-52 !tw-min-w-52">
<label>{{ "region" | i18n }}:</label>
<span *ngIf="hasFlags" class="!tw-inline-block !tw-min-w-52">
<label>{{ "server" | i18n }}:</label>
<a
[routerLink]="[]"
[bitMenuTriggerFor]="environmentOptions"
class="tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-2"
>
<img
[src]="'../../images/' + selectedRegionImageName + '.png'"
[src]="'../../images/' + selectedRegionImageName + '.svg'"
alt="{{ 'selectedRegionFlag' | i18n }}"
class="pb-1 mr-1 tw-ml-1"
class="tw-mx-1 tw-pb-1"
/><label class="tw-cursor-pointer !tw-font-normal"
>{{ isEuServer ? ("eu" | i18n) : ("us" | i18n) }}
>{{ isEuServer ? ("euDomain" | i18n) : ("usDomain" | i18n) }}
<i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i
></label>
</a>
</span>
<div *ngIf="!hasFlags">
{{ "region" | i18n }}:
{{ "server" | i18n }}:
<a [routerLink]="[]" [bitMenuTriggerFor]="environmentOptions">
<b>{{ isEuServer ? ("eu" | i18n) : ("us" | i18n) }}</b
<b>{{ isEuServer ? ("euDomain" | i18n) : ("usDomain" | i18n) }}</b
><i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
</a>
</div>

View File

@@ -35,9 +35,9 @@ export class EnvironmentSelectorComponent implements OnInit {
getRegionImage(): string {
if (this.isEuServer) {
return "eu_flag";
return "flag-eu";
} else {
return "us_flag";
return "flag-us";
}
}
}

View File

@@ -17,7 +17,6 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/platform/abstractions/state-migration.service";
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
@@ -27,7 +26,6 @@ import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@
import { PolicyListService } from "../admin-console/core/policy-list.service";
import { HtmlStorageService } from "../core/html-storage.service";
import { I18nService } from "../core/i18n.service";
import { StateMigrationService } from "../core/state-migration.service";
import { CollectionAdminService } from "../vault/core/collection-admin.service";
import { PasswordRepromptService } from "../vault/core/password-reprompt.service";
@@ -84,11 +82,6 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service";
},
{ provide: MessagingServiceAbstraction, useClass: BroadcasterMessagingService },
{ provide: ModalServiceAbstraction, useClass: ModalService },
{
provide: StateMigrationServiceAbstraction,
useClass: StateMigrationService,
deps: [AbstractStorageService, SECURE_STORAGE, STATE_FACTORY],
},
StateService,
{
provide: BaseStateServiceAbstraction,

View File

@@ -80,6 +80,9 @@ export class EventService {
case EventType.User_MigratedKeyToKeyConnector:
msg = humanReadableMsg = this.i18nService.t("migratedKeyConnector");
break;
case EventType.User_RequestedDeviceApproval:
msg = humanReadableMsg = this.i18nService.t("requestedDeviceApproval");
break;
// Cipher
case EventType.Cipher_Created:
msg = this.i18nService.t("createdItemId", this.formatCipherId(ev, options));
@@ -307,6 +310,20 @@ export class EventService {
this.getShortId(ev.organizationUserId)
);
break;
case EventType.OrganizationUser_ApprovedAuthRequest:
msg = this.i18nService.t("approvedAuthRequest", this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t(
"approvedAuthRequest",
this.getShortId(ev.organizationUserId)
);
break;
case EventType.OrganizationUser_RejectedAuthRequest:
msg = this.i18nService.t("rejectedAuthRequest", this.formatOrgUserId(ev));
humanReadableMsg = this.i18nService.t(
"rejectedAuthRequest",
this.getShortId(ev.organizationUserId)
);
break;
// Org
case EventType.Organization_Updated:
msg = humanReadableMsg = this.i18nService.t("editedOrgSettings");

View File

@@ -4,7 +4,6 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@@ -16,7 +15,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { VaultTimeoutService as VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
import { I18nService } from "../core/i18n.service";
@@ -26,7 +25,7 @@ export class InitService {
@Inject(WINDOW) private win: Window,
private environmentService: EnvironmentServiceAbstraction,
private notificationsService: NotificationsServiceAbstraction,
private vaultTimeoutService: VaultTimeoutServiceAbstraction,
private vaultTimeoutService: VaultTimeoutService,
private i18nService: I18nServiceAbstraction,
private eventUploadService: EventUploadServiceAbstraction,
private twoFactorService: TwoFactorServiceAbstraction,
@@ -48,7 +47,7 @@ export class InitService {
this.environmentService.initialized = true;
setTimeout(() => this.notificationsService.init(), 3000);
(this.vaultTimeoutService as VaultTimeoutService).init(true);
await this.vaultTimeoutService.init(true);
const locale = await this.stateService.getLocale();
await (this.i18nService as I18nService).init(locale);
(this.eventUploadService as EventUploadService).init(true);

View File

@@ -23,7 +23,12 @@ export class RouterService {
.subscribe((event: NavigationEnd) => {
this.currentUrl = event.url;
let title = i18nService.t("pageTitle", "Bitwarden");
let title = i18nService.t("bitWebVault");
if (this.currentUrl.includes("/sm/")) {
title = i18nService.t("bitSecretsManager");
}
let child = this.activatedRoute.firstChild;
while (child.firstChild) {
child = child.firstChild;

View File

@@ -1,13 +0,0 @@
import { StateMigrationService as BaseStateMigrationService } from "@bitwarden/common/platform/services/state-migration.service";
import { Account } from "./state/account";
import { GlobalState } from "./state/global-state";
export class StateMigrationService extends BaseStateMigrationService<GlobalState, Account> {
protected async migrationStateFrom1To2(): Promise<void> {
await super.migrateStateFrom1To2();
const globals = (await this.get<GlobalState>("global")) ?? this.stateFactory.createGlobal(null);
globals.rememberEmail = (await this.get<boolean>("rememberEmail")) ?? globals.rememberEmail;
await this.set("global", globals);
}
}

View File

@@ -7,7 +7,6 @@ import {
STATE_SERVICE_USE_CACHE,
} from "@bitwarden/angular/services/injection-tokens";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateMigrationService } from "@bitwarden/common/platform/abstractions/state-migration.service";
import {
AbstractMemoryStorageService,
AbstractStorageService,
@@ -30,7 +29,6 @@ export class StateService extends BaseStateService<GlobalState, Account> {
@Inject(SECURE_STORAGE) secureStorageService: AbstractStorageService,
@Inject(MEMORY_STORAGE) memoryStorageService: AbstractMemoryStorageService,
logService: LogService,
stateMigrationService: StateMigrationService,
@Inject(STATE_FACTORY) stateFactory: StateFactory<GlobalState, Account>,
@Inject(STATE_SERVICE_USE_CACHE) useAccountCache = true
) {
@@ -39,7 +37,6 @@ export class StateService extends BaseStateService<GlobalState, Account> {
secureStorageService,
memoryStorageService,
logService,
stateMigrationService,
stateFactory,
useAccountCache
);

View File

@@ -1,24 +0,0 @@
import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
@Injectable({
providedIn: "root",
})
export class HomeGuard implements CanActivate {
constructor(private router: Router, private authService: AuthService) {}
async canActivate(route: ActivatedRouteSnapshot) {
const authStatus = await this.authService.getAuthStatus();
if (authStatus === AuthenticationStatus.LoggedOut) {
return this.router.createUrlTree(["/login"], { queryParams: route.queryParams });
}
if (authStatus === AuthenticationStatus.Locked) {
return this.router.createUrlTree(["/lock"], { queryParams: route.queryParams });
}
return this.router.createUrlTree(["/vault"], { queryParams: route.queryParams });
}
}

View File

@@ -1,6 +1,6 @@
<nav class="navbar navbar-expand navbar-dark" [ngClass]="{ 'nav-background-alt': selfHosted }">
<div class="container">
<a class="navbar-brand" routerLink="/" appA11yTitle="{{ 'pageTitle' | i18n : 'Bitwarden' }}">
<a class="navbar-brand" routerLink="/" appA11yTitle="{{ 'bitWebVault' | i18n }}">
<i class="bwi bwi-shield" aria-hidden="true"></i>
</a>
<div class="collapse navbar-collapse">
@@ -84,7 +84,7 @@
{{ "getApps" | i18n }}
</a>
<bit-menu-divider></bit-menu-divider>
<button bitMenuItem type="button" (click)="lock()">
<button *ngIf="canLock$ | async" bitMenuItem type="button" (click)="lock()">
<i class="bwi bwi-fw bwi-lock" aria-hidden="true"></i>
{{ "lockNow" | i18n }}
</button>

View File

@@ -1,6 +1,7 @@
import { Component, OnInit } from "@angular/core";
import { Observable } from "rxjs";
import { map, Observable } from "rxjs";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import {
canAccessAdmin,
OrganizationService,
@@ -8,6 +9,7 @@ import {
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { Provider } from "@bitwarden/common/models/domain/provider";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@@ -26,6 +28,7 @@ export class NavbarComponent implements OnInit {
providers: Provider[] = [];
userId: string;
organizations$: Observable<Organization[]>;
canLock$: Observable<boolean>;
constructor(
private messagingService: MessagingService,
@@ -34,6 +37,7 @@ export class NavbarComponent implements OnInit {
private providerService: ProviderService,
private syncService: SyncService,
private organizationService: OrganizationService,
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
private i18nService: I18nService
) {
this.selfHosted = this.platformUtilsService.isSelfHost();
@@ -56,6 +60,9 @@ export class NavbarComponent implements OnInit {
this.organizations$ = this.organizationService.memberOrganizations$.pipe(
canAccessAdmin(this.i18nService)
);
this.canLock$ = this.vaultTimeoutSettingsService
.availableVaultTimeoutActions$()
.pipe(map((actions) => actions.includes(VaultTimeoutAction.Lock)));
}
lock() {

View File

@@ -64,7 +64,7 @@ export class ProductSwitcherContentComponent {
isActive: !this.router.url.includes("/sm/"),
},
sm: {
name: "Secrets Manager Beta",
name: "Secrets Manager",
icon: "bwi-cli",
appRoute: ["/sm", smOrg?.id],
marketingRoute: "https://bitwarden.com/products/secrets-manager/",

View File

@@ -1,9 +1,15 @@
import { NgModule } from "@angular/core";
import { Route, RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "@bitwarden/angular/auth/guards/auth.guard";
import { LockGuard } from "@bitwarden/angular/auth/guards/lock.guard";
import { UnauthGuard } from "@bitwarden/angular/auth/guards/unauth.guard";
import {
AuthGuard,
lockGuard,
redirectGuard,
tdeDecryptionRequiredGuard,
UnauthGuard,
} from "@bitwarden/angular/auth/guards";
import { canAccessFeature } from "@bitwarden/angular/guard/feature-flag.guard";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { SubscriptionRoutingModule } from "../app/billing/settings/subscription-routing.module";
import { flagEnabled, Flags } from "../utils/flags";
@@ -16,6 +22,7 @@ import { AcceptEmergencyComponent } from "./auth/accept-emergency.component";
import { AcceptOrganizationComponent } from "./auth/accept-organization.component";
import { HintComponent } from "./auth/hint.component";
import { LockComponent } from "./auth/lock.component";
import { LoginDecryptionOptionsComponent } from "./auth/login/login-decryption-options/login-decryption-options.component";
import { LoginWithDeviceComponent } from "./auth/login/login-with-device.component";
import { LoginComponent } from "./auth/login/login.component";
import { RecoverDeleteComponent } from "./auth/recover-delete.component";
@@ -31,7 +38,6 @@ import { UpdatePasswordComponent } from "./auth/update-password.component";
import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component";
import { HomeGuard } from "./guards/home.guard";
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
import { UserLayoutComponent } from "./layouts/user-layout.component";
import { ReportsModule } from "./reports";
@@ -56,7 +62,7 @@ const routes: Routes = [
path: "",
pathMatch: "full",
children: [], // Children lets us have an empty component.
canActivate: [HomeGuard], // Redirects either to vault, login or lock page.
canActivate: [redirectGuard()], // Redirects either to vault, login, or lock page.
},
{ path: "login", component: LoginComponent, canActivate: [UnauthGuard] },
{
@@ -64,7 +70,20 @@ const routes: Routes = [
component: LoginWithDeviceComponent,
data: { titleId: "loginWithDevice" },
},
{
path: "admin-approval-requested",
component: LoginWithDeviceComponent,
data: { titleId: "loginWithDevice" },
},
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
{
path: "login-initiated",
component: LoginDecryptionOptionsComponent,
canActivate: [
tdeDecryptionRequiredGuard(),
canAccessFeature(FeatureFlag.TrustedDeviceEncryption),
],
},
{
path: "register",
component: TrialInitiationComponent,
@@ -96,7 +115,7 @@ const routes: Routes = [
{
path: "lock",
component: LockComponent,
canActivate: [LockGuard],
canActivate: [lockGuard()],
},
{ path: "verify-email", component: VerifyEmailTokenComponent },
{

View File

@@ -1,7 +1,7 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "@bitwarden/angular/auth/guards/auth.guard";
import { AuthGuard } from "@bitwarden/angular/auth/guards";
import { HasPremiumGuard } from "../core/guards/has-premium.guard";

View File

@@ -1,9 +1,7 @@
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { DeauthorizeSessionsComponent } from "../auth/settings/deauthorize-sessions.component";
@@ -26,13 +24,11 @@ export class AccountComponent {
constructor(
private modalService: ModalService,
private apiService: ApiService,
private keyConnectorService: KeyConnectorService,
private stateService: StateService
private userVerificationService: UserVerificationService
) {}
async ngOnInit() {
this.showChangeEmail = !(await this.keyConnectorService.getUsesKeyConnector());
this.showChangeEmail = await this.userVerificationService.hasMasterPassword();
}
async deauthorizeSessions() {

View File

@@ -42,8 +42,8 @@ export class ChangeEmailComponent implements OnInit {
}
async submit() {
const hasEncKey = await this.cryptoService.hasEncKey();
if (!hasEncKey) {
const hasUserKey = await this.cryptoService.hasUserKey();
if (!hasUserKey) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("updateKey"));
return;
}
@@ -52,7 +52,10 @@ export class ChangeEmailComponent implements OnInit {
if (!this.tokenSent) {
const request = new EmailTokenRequest();
request.newEmail = this.newEmail;
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
this.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(this.masterPassword)
);
try {
this.formPromise = this.apiService.postEmailToken(request);
await this.formPromise;
@@ -64,21 +67,24 @@ export class ChangeEmailComponent implements OnInit {
const request = new EmailRequest();
request.token = this.token;
request.newEmail = this.newEmail;
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
this.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(this.masterPassword)
);
const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig();
const newKey = await this.cryptoService.makeKey(
const newMasterKey = await this.cryptoService.makeMasterKey(
this.masterPassword,
this.newEmail,
kdf,
kdfConfig
);
request.newMasterPasswordHash = await this.cryptoService.hashPassword(
request.newMasterPasswordHash = await this.cryptoService.hashMasterKey(
this.masterPassword,
newKey
newMasterKey
);
const newEncKey = await this.cryptoService.remakeEncKey(newKey);
request.key = newEncKey[1].encryptedString;
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(newMasterKey);
request.key = newUserKey[1].encryptedString;
try {
this.formPromise = this.apiService.postEmail(request);
await this.formPromise;

View File

@@ -46,8 +46,8 @@ export class ChangeKdfConfirmationComponent {
async submit() {
this.loading = true;
const hasEncKey = await this.cryptoService.hasEncKey();
if (!hasEncKey) {
const hasUserKey = await this.cryptoService.hasUserKey();
if (!hasUserKey) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("updateKey"));
return;
}
@@ -75,17 +75,21 @@ export class ChangeKdfConfirmationComponent {
request.kdfIterations = this.kdfConfig.iterations;
request.kdfMemory = this.kdfConfig.memory;
request.kdfParallelism = this.kdfConfig.parallelism;
request.masterPasswordHash = await this.cryptoService.hashPassword(masterPassword, null);
const masterKey = await this.cryptoService.getOrDeriveMasterKey(masterPassword);
request.masterPasswordHash = await this.cryptoService.hashMasterKey(masterPassword, masterKey);
const email = await this.stateService.getEmail();
const newKey = await this.cryptoService.makeKey(
const newMasterKey = await this.cryptoService.makeMasterKey(
masterPassword,
email,
this.kdf,
this.kdfConfig
);
request.newMasterPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey);
const newEncKey = await this.cryptoService.remakeEncKey(newKey);
request.key = newEncKey[1].encryptedString;
request.newMasterPasswordHash = await this.cryptoService.hashMasterKey(
masterPassword,
newMasterKey
);
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(newMasterKey);
request.key = newUserKey[1].encryptedString;
await this.apiService.postAccountKdf(request);
}

View File

@@ -1,6 +1,5 @@
import { Component, OnInit } from "@angular/core";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import {
DEFAULT_KDF_CONFIG,
@@ -11,6 +10,7 @@ import {
KdfType,
} from "@bitwarden/common/enums";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DialogService } from "@bitwarden/components";
import { ChangeKdfConfirmationComponent } from "./change-kdf-confirmation.component";
@@ -25,7 +25,7 @@ export class ChangeKdfComponent implements OnInit {
kdfOptions: any[] = [];
recommendedPbkdf2Iterations = DEFAULT_PBKDF2_ITERATIONS;
constructor(private stateService: StateService, private dialogService: DialogServiceAbstraction) {
constructor(private stateService: StateService, private dialogService: DialogService) {
this.kdfOptions = [
{ name: "PBKDF2 SHA-256", value: KdfType.PBKDF2_SHA256 },
{ name: "Argon2id", value: KdfType.Argon2id },

View File

@@ -27,37 +27,45 @@
</app-vault-timeout-input>
</div>
</div>
<div class="form-group">
<label>{{ "vaultTimeoutAction" | i18n }}</label>
<div class="form-check form-check-block">
<input
class="form-check-input"
type="radio"
name="vaultTimeoutAction"
id="vaultTimeoutActionLock"
value="{{ VaultTimeoutAction.Lock }}"
formControlName="vaultTimeoutAction"
/>
<label class="form-check-label" for="vaultTimeoutActionLock">
{{ "lock" | i18n }}
<small>{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
</label>
<ng-container *ngIf="availableVaultTimeoutActions$ | async as availableVaultTimeoutActions">
<div *ngIf="availableVaultTimeoutActions.length > 1" class="form-group">
<label>{{ "vaultTimeoutAction" | i18n }}</label>
<div
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)"
class="form-check form-check-block"
>
<input
class="form-check-input"
type="radio"
name="vaultTimeoutAction"
id="vaultTimeoutActionLock"
value="{{ VaultTimeoutAction.Lock }}"
formControlName="vaultTimeoutAction"
/>
<label class="form-check-label" for="vaultTimeoutActionLock">
{{ "lock" | i18n }}
<small>{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
</label>
</div>
<div
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.LogOut)"
class="form-check mt-2 form-check-block"
>
<input
class="form-check-input"
type="radio"
name="vaultTimeoutAction"
id="vaultTimeoutActionLogOut"
value="{{ VaultTimeoutAction.LogOut }}"
formControlName="vaultTimeoutAction"
/>
<label class="form-check-label" for="vaultTimeoutActionLogOut">
{{ "logOut" | i18n }}
<small>{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
</label>
</div>
</div>
<div class="form-check mt-2 form-check-block">
<input
class="form-check-input"
type="radio"
name="vaultTimeoutAction"
id="vaultTimeoutActionLogOut"
value="{{ VaultTimeoutAction.LogOut }}"
formControlName="vaultTimeoutAction"
/>
<label class="form-check-label" for="vaultTimeoutActionLogOut">
{{ "logOut" | i18n }}
<small>{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
</label>
</div>
</div>
</ng-container>
<div class="row">
<div class="col-6">
<div class="form-group">

View File

@@ -1,11 +1,10 @@
import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { concatMap, filter, map, Observable, Subject, takeUntil, tap } from "rxjs";
import { concatMap, filter, firstValueFrom, map, Observable, Subject, takeUntil, tap } from "rxjs";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { ThemeType } from "@bitwarden/common/enums";
@@ -15,6 +14,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "app-preferences",
@@ -24,6 +24,8 @@ export class PreferencesComponent implements OnInit {
// For use in template
protected readonly VaultTimeoutAction = VaultTimeoutAction;
protected availableVaultTimeoutActions$: Observable<VaultTimeoutAction[]>;
vaultTimeoutPolicyCallout: Observable<{
timeout: { hours: number; minutes: number };
action: VaultTimeoutAction;
@@ -55,7 +57,7 @@ export class PreferencesComponent implements OnInit {
private messagingService: MessagingService,
private themingService: AbstractThemingService,
private settingsService: SettingsService,
private dialogService: DialogServiceAbstraction
private dialogService: DialogService
) {
this.vaultTimeoutOptions = [
{ name: i18nService.t("oneMinute"), value: 1 },
@@ -89,6 +91,9 @@ export class PreferencesComponent implements OnInit {
}
async ngOnInit() {
this.availableVaultTimeoutActions$ =
this.vaultTimeoutSettingsService.availableVaultTimeoutActions$();
this.vaultTimeoutPolicyCallout = this.policyService.get$(PolicyType.MaximumVaultTimeout).pipe(
filter((policy) => policy != null),
map((policy) => {
@@ -117,7 +122,7 @@ export class PreferencesComponent implements OnInit {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "vaultTimeoutLogOutConfirmationTitle" },
content: { key: "vaultTimeoutLogOutConfirmation" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (!confirmed) {
@@ -133,7 +138,9 @@ export class PreferencesComponent implements OnInit {
.subscribe();
const initialFormValues = {
vaultTimeout: await this.vaultTimeoutSettingsService.getVaultTimeout(),
vaultTimeoutAction: await this.vaultTimeoutSettingsService.getVaultTimeoutAction(),
vaultTimeoutAction: await firstValueFrom(
this.vaultTimeoutSettingsService.vaultTimeoutAction$()
),
enableFavicons: !(await this.settingsService.getDisableFavicon()),
enableFullWidth: await this.stateService.getEnableFullWidth(),
theme: await this.stateService.getTheme(),

View File

@@ -2,7 +2,7 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ApiKeyComponent } from "./api-key.component";
@@ -20,14 +20,14 @@ export class SecurityKeysComponent implements OnInit {
showChangeKdf = true;
constructor(
private keyConnectorService: KeyConnectorService,
private userVerificationService: UserVerificationService,
private stateService: StateService,
private modalService: ModalService,
private apiService: ApiService
) {}
async ngOnInit() {
this.showChangeKdf = !(await this.keyConnectorService.getUsesKeyConnector());
this.showChangeKdf = await this.userVerificationService.hasMasterPassword();
}
async viewUserApiKey() {

View File

@@ -1,9 +1,9 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { ChangePasswordComponent } from "../auth/settings/change-password.component";
import { TwoFactorSetupComponent } from "../auth/settings/two-factor-setup.component";
import { ChangePasswordComponent } from "./change-password.component";
import { SecurityKeysComponent } from "./security-keys.component";
import { SecurityComponent } from "./security.component";

View File

@@ -1,6 +1,6 @@
import { Component } from "@angular/core";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
@Component({
selector: "app-security",
@@ -9,9 +9,9 @@ import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-con
export class SecurityComponent {
showChangePassword = true;
constructor(private keyConnectorService: KeyConnectorService) {}
constructor(private userVerificationService: UserVerificationService) {}
async ngOnInit() {
this.showChangePassword = !(await this.keyConnectorService.getUsesKeyConnector());
this.showChangePassword = await this.userVerificationService.hasMasterPassword();
}
}

View File

@@ -1,4 +1,4 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="updateEncKeyTitle">
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="updateUserKeyTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form
class="modal-content"
@@ -8,7 +8,7 @@
ngNativeValidate
>
<div class="modal-header">
<h1 class="modal-title" id="updateEncKeyTitle">{{ "updateEncryptionKey" | i18n }}</h1>
<h1 class="modal-title" id="updateUserKeyTitle">{{ "updateEncryptionKey" | i18n }}</h1>
<button
type="button"
class="close"

View File

@@ -36,8 +36,8 @@ export class UpdateKeyComponent {
) {}
async submit() {
const hasEncKey = await this.cryptoService.hasEncKey();
if (hasEncKey) {
const hasUserKey = await this.cryptoService.hasUserKey();
if (hasUserKey) {
return;
}
@@ -68,17 +68,20 @@ export class UpdateKeyComponent {
}
private async makeRequest(): Promise<UpdateKeyRequest> {
const key = await this.cryptoService.getKey();
const encKey = await this.cryptoService.makeEncKey(key);
const masterKey = await this.cryptoService.getMasterKey();
const newUserKey = await this.cryptoService.makeUserKey(masterKey);
const privateKey = await this.cryptoService.getPrivateKey();
let encPrivateKey: EncString = null;
if (privateKey != null) {
encPrivateKey = await this.cryptoService.encrypt(privateKey, encKey[0]);
encPrivateKey = await this.cryptoService.encrypt(privateKey, newUserKey[0]);
}
const request = new UpdateKeyRequest();
request.privateKey = encPrivateKey != null ? encPrivateKey.encryptedString : null;
request.key = encKey[1].encryptedString;
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
request.key = newUserKey[1].encryptedString;
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
this.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(this.masterPassword)
);
await this.syncService.fullSync(true);
@@ -87,7 +90,7 @@ export class UpdateKeyComponent {
if (folders[i].id == null) {
continue;
}
const folder = await this.folderService.encrypt(folders[i], encKey[0]);
const folder = await this.folderService.encrypt(folders[i], newUserKey[0]);
request.folders.push(new FolderWithIdRequest(folder));
}
@@ -96,7 +99,7 @@ export class UpdateKeyComponent {
if (ciphers[i].organizationId != null) {
continue;
}
const cipher = await this.cipherService.encrypt(ciphers[i], encKey[0]);
const cipher = await this.cipherService.encrypt(ciphers[i], newUserKey[0]);
request.ciphers.push(new CipherWithIdRequest(cipher));
}

View File

@@ -9,7 +9,9 @@
>
<option *ngFor="let o of vaultTimeoutOptions" [ngValue]="o.value">{{ o.name }}</option>
</select>
<small class="form-text text-muted">{{ "vaultTimeoutDesc" | i18n }}</small>
<small class="form-text text-muted">{{
((canLockVault$ | async) ? "vaultTimeoutDesc" : "vaultTimeoutLogoutDesc") | i18n
}}</small>
</div>
<div class="form-group" *ngIf="showCustom" formGroupName="custom">
<label for="customVaultTimeout">{{ "customVaultTimeout" | i18n }}</label>

View File

@@ -12,7 +12,7 @@ import { SharedModule } from "../../shared.module";
})
export class AccountFingerprintComponent implements OnInit {
@Input() fingerprintMaterial: string;
@Input() publicKeyBuffer: ArrayBuffer;
@Input() publicKeyBuffer: Uint8Array;
@Input() fingerprintLabel: string;
protected fingerprint: string;

View File

@@ -26,6 +26,7 @@ import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component"
import { RegisterFormModule } from "../auth/register-form/register-form.module";
import { RemovePasswordComponent } from "../auth/remove-password.component";
import { SetPasswordComponent } from "../auth/set-password.component";
import { ChangePasswordComponent } from "../auth/settings/change-password.component";
import { DeauthorizeSessionsComponent } from "../auth/settings/deauthorize-sessions.component";
import { EmergencyAccessAddEditComponent } from "../auth/settings/emergency-access/emergency-access-add-edit.component";
import { EmergencyAccessAttachmentsComponent } from "../auth/settings/emergency-access/emergency-access-attachments.component";
@@ -76,12 +77,10 @@ import { ApiKeyComponent } from "../settings/api-key.component";
import { ChangeAvatarComponent } from "../settings/change-avatar.component";
import { ChangeEmailComponent } from "../settings/change-email.component";
import { ChangeKdfModule } from "../settings/change-kdf/change-kdf.module";
import { ChangePasswordComponent } from "../settings/change-password.component";
import { DeleteAccountComponent } from "../settings/delete-account.component";
import { DomainRulesComponent } from "../settings/domain-rules.component";
import { LowKdfComponent } from "../settings/low-kdf.component";
import { PreferencesComponent } from "../settings/preferences.component";
import { PremiumComponent } from "../settings/premium.component";
import { ProfileComponent } from "../settings/profile.component";
import { PurgeVaultComponent } from "../settings/purge-vault.component";
import { SecurityKeysComponent } from "../settings/security-keys.component";
@@ -95,7 +94,6 @@ import { PasswordGeneratorHistoryComponent } from "../tools/password-generator-h
import { AccessComponent } from "../tools/send/access.component";
import { AddEditComponent as SendAddEditComponent } from "../tools/send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "../tools/send/efflux-dates.component";
import { SendComponent } from "../tools/send/send.component";
import { ToolsComponent } from "../tools/tools.component";
import { PasswordRepromptComponent } from "../vault/components/password-reprompt.component";
import { PremiumBadgeComponent } from "../vault/components/premium-badge.component";
@@ -108,6 +106,7 @@ import { ShareComponent } from "../vault/individual-vault/share.component";
import { AddEditComponent as OrgAddEditComponent } from "../vault/org-vault/add-edit.component";
import { AttachmentsComponent as OrgAttachmentsComponent } from "../vault/org-vault/attachments.component";
import { CollectionsComponent as OrgCollectionsComponent } from "../vault/org-vault/collections.component";
import { PremiumComponent } from "../vault/settings/premium.component";
import { EnvironmentSelectorModule } from "./../components/environment-selector/environment-selector.module";
import { AccountFingerprintComponent } from "./components/account-fingerprint/account-fingerprint.component";
@@ -199,7 +198,6 @@ import { SharedModule } from "./shared.module";
SecurityKeysComponent,
SelectableAvatarComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SetPasswordComponent,
SettingsComponent,
@@ -304,7 +302,6 @@ import { SharedModule } from "./shared.module";
SecurityKeysComponent,
SelectableAvatarComponent,
SendAddEditComponent,
SendComponent,
SendEffluxDatesComponent,
SetPasswordComponent,
SettingsComponent,

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