mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 09:13:33 +00:00
Merge branch 'main' into ps/PM-7846-rust-ipc
This commit is contained in:
@@ -1483,6 +1483,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewItemHeader": {
|
||||
"message": "View $TYPE$",
|
||||
"placeholders": {
|
||||
"type": {
|
||||
"content": "$1",
|
||||
"example": "Login"
|
||||
}
|
||||
}
|
||||
},
|
||||
"passwordHistory": {
|
||||
"message": "Password history"
|
||||
},
|
||||
@@ -3535,6 +3544,30 @@
|
||||
"contactYourOrgAdmin": {
|
||||
"message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance."
|
||||
},
|
||||
"itemDetails": {
|
||||
"message": "Item details"
|
||||
},
|
||||
"itemName": {
|
||||
"message": "Item name"
|
||||
},
|
||||
"additionalInformation": {
|
||||
"message": "Additional information"
|
||||
},
|
||||
"itemHistory": {
|
||||
"message": "Item history"
|
||||
},
|
||||
"lastEdited": {
|
||||
"message": "Last edited"
|
||||
},
|
||||
"ownerYou":{
|
||||
"message": "Owner: You"
|
||||
},
|
||||
"linked": {
|
||||
"message": "Linked"
|
||||
},
|
||||
"copySuccessful": {
|
||||
"message": "Copy Successful"
|
||||
},
|
||||
"upload": {
|
||||
"message": "Upload"
|
||||
},
|
||||
@@ -3574,6 +3607,15 @@
|
||||
"filters": {
|
||||
"message": "Filters"
|
||||
},
|
||||
"downloadAttachment": {
|
||||
"message": "Download - $ITEMNAME$",
|
||||
"placeholders": {
|
||||
"itemname": {
|
||||
"content": "$1",
|
||||
"example": "Your File"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cardDetails": {
|
||||
"message": "Card details"
|
||||
},
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<footer
|
||||
class="tw-p-3 tw-border-0 tw-border-solid tw-border-t tw-border-secondary-300 tw-bg-background"
|
||||
>
|
||||
<div class="tw-max-w-screen-sm tw-mx-auto">
|
||||
<div class="tw-max-w-screen-sm tw-mx-auto tw-flex tw-justify-between tw-w-full">
|
||||
<div class="tw-flex tw-justify-start tw-gap-2">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
<div class="tw-inline-flex tw-items-center tw-gap-2 tw-h-9">
|
||||
<ng-content select="[slot=end]"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -266,6 +266,7 @@ class MockSettingsPageComponent {}
|
||||
<popup-footer slot="footer">
|
||||
<button bitButton buttonType="primary">Save</button>
|
||||
<button bitButton buttonType="secondary">Cancel</button>
|
||||
<button slot="end" type="button" buttonType="danger" bitIconButton="bwi-trash"></button>
|
||||
</popup-footer>
|
||||
</popup-page>
|
||||
`,
|
||||
@@ -279,6 +280,7 @@ class MockSettingsPageComponent {}
|
||||
MockPopoutButtonComponent,
|
||||
MockCurrentAccountComponent,
|
||||
VaultComponent,
|
||||
IconButtonModule,
|
||||
],
|
||||
})
|
||||
class MockVaultSubpageComponent {}
|
||||
|
||||
@@ -71,6 +71,7 @@ import { VaultV2Component } from "../vault/popup/components/vault/vault-v2.compo
|
||||
import { ViewComponent } from "../vault/popup/components/vault/view.component";
|
||||
import { AddEditV2Component } from "../vault/popup/components/vault-v2/add-edit/add-edit-v2.component";
|
||||
import { AttachmentsV2Component } from "../vault/popup/components/vault-v2/attachments/attachments-v2.component";
|
||||
import { ViewV2Component } from "../vault/popup/components/vault-v2/view-v2/view-v2.component";
|
||||
import { AppearanceComponent } from "../vault/popup/settings/appearance.component";
|
||||
import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component";
|
||||
import { FoldersComponent } from "../vault/popup/settings/folders.component";
|
||||
@@ -211,12 +212,11 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
data: { state: "ciphers" },
|
||||
},
|
||||
{
|
||||
...extensionRefreshSwap(ViewComponent, ViewV2Component, {
|
||||
path: "view-cipher",
|
||||
component: ViewComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: { state: "view-cipher" },
|
||||
},
|
||||
}),
|
||||
{
|
||||
path: "cipher-password-history",
|
||||
component: PasswordHistoryComponent,
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<a
|
||||
bit-item-content
|
||||
[routerLink]="['/view-cipher']"
|
||||
[queryParams]="{ cipherId: cipher.id }"
|
||||
[queryParams]="{ cipherId: cipher.id, type: cipher.type }"
|
||||
[appA11yTitle]="'viewItemTitle' | i18n: cipher.name"
|
||||
>
|
||||
<app-vault-icon slot="start" [cipher]="cipher"></app-vault-icon>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<popup-page>
|
||||
<popup-header slot="header" [pageTitle]="headerText" showBackButton> </popup-header>
|
||||
|
||||
<app-cipher-view *ngIf="cipher" [cipher]="cipher"></app-cipher-view>
|
||||
|
||||
<popup-footer slot="footer">
|
||||
<a bitButton type="button" buttonType="primary" (click)="editCipher()">
|
||||
{{ "edit" | i18n }}
|
||||
</a>
|
||||
<button
|
||||
slot="end"
|
||||
*ngIf="cipher && cipher.edit"
|
||||
[bitAction]="delete"
|
||||
type="button"
|
||||
buttonType="danger"
|
||||
bitIconButton="bwi-trash"
|
||||
[appA11yTitle]="'delete' | i18n"
|
||||
></button>
|
||||
</popup-footer>
|
||||
</popup-page>
|
||||
@@ -0,0 +1,157 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Observable, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
SearchModule,
|
||||
ButtonModule,
|
||||
IconButtonModule,
|
||||
DialogService,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { CipherViewComponent } from "../../../../../../../../libs/vault/src/cipher-view";
|
||||
|
||||
import { PopupFooterComponent } from "./../../../../../platform/popup/layout/popup-footer.component";
|
||||
import { PopupHeaderComponent } from "./../../../../../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "./../../../../../platform/popup/layout/popup-page.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-view-v2",
|
||||
templateUrl: "view-v2.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
SearchModule,
|
||||
JslibModule,
|
||||
FormsModule,
|
||||
ButtonModule,
|
||||
PopupPageComponent,
|
||||
PopupHeaderComponent,
|
||||
PopupFooterComponent,
|
||||
IconButtonModule,
|
||||
CipherViewComponent,
|
||||
AsyncActionsModule,
|
||||
],
|
||||
})
|
||||
export class ViewV2Component {
|
||||
headerText: string;
|
||||
cipherId: string;
|
||||
cipher: CipherView;
|
||||
organization$: Observable<Organization>;
|
||||
folder$: Observable<FolderView>;
|
||||
collections$: Observable<CollectionView[]>;
|
||||
private passwordReprompted = false;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private i18nService: I18nService,
|
||||
private cipherService: CipherService,
|
||||
private passwordRepromptService: PasswordRepromptService,
|
||||
private dialogService: DialogService,
|
||||
private logService: LogService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
this.subscribeToParams();
|
||||
}
|
||||
|
||||
subscribeToParams(): void {
|
||||
this.route.queryParams
|
||||
.pipe(
|
||||
switchMap((param) => {
|
||||
return this.getCipherData(param.cipherId);
|
||||
}),
|
||||
takeUntilDestroyed(),
|
||||
)
|
||||
.subscribe((data) => {
|
||||
this.cipher = data;
|
||||
this.headerText = this.setHeader(data.type);
|
||||
});
|
||||
}
|
||||
|
||||
setHeader(type: CipherType) {
|
||||
switch (type) {
|
||||
case CipherType.Login:
|
||||
return this.i18nService.t("viewItemHeader", this.i18nService.t("typeLogin"));
|
||||
case CipherType.Card:
|
||||
return this.i18nService.t("viewItemHeader", this.i18nService.t("typeCard"));
|
||||
case CipherType.Identity:
|
||||
return this.i18nService.t("viewItemHeader", this.i18nService.t("typeIdentity"));
|
||||
case CipherType.SecureNote:
|
||||
return this.i18nService.t("viewItemHeader", this.i18nService.t("note"));
|
||||
}
|
||||
}
|
||||
|
||||
async getCipherData(id: string) {
|
||||
const cipher = await this.cipherService.get(id);
|
||||
return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher));
|
||||
}
|
||||
|
||||
editCipher() {
|
||||
if (this.cipher.isDeleted) {
|
||||
return false;
|
||||
}
|
||||
void this.router.navigate(["/edit-cipher"], {
|
||||
queryParams: { cipherId: this.cipher.id, type: this.cipher.type, isNew: false },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
delete = async (): Promise<boolean> => {
|
||||
this.passwordReprompted =
|
||||
this.passwordReprompted ||
|
||||
(await this.passwordRepromptService.passwordRepromptCheck(this.cipher));
|
||||
if (!this.passwordReprompted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "deleteItem" },
|
||||
content: {
|
||||
key: this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation",
|
||||
},
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.deleteCipher();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.router.navigate(["/vault"]);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem"),
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
protected deleteCipher() {
|
||||
return this.cipher.isDeleted
|
||||
? this.cipherService.deleteWithServer(this.cipher.id)
|
||||
: this.cipherService.softDeleteWithServer(this.cipher.id);
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,9 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn
|
||||
super.selectCipher(cipher);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/view-cipher"], { queryParams: { cipherId: cipher.id } });
|
||||
this.router.navigate(["/view-cipher"], {
|
||||
queryParams: { cipherId: cipher.id },
|
||||
});
|
||||
}
|
||||
this.preventSelected = false;
|
||||
}, 200);
|
||||
|
||||
@@ -1,66 +1,27 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="text-center mb-4">
|
||||
<i class="bwi bwi-lock bwi-4x text-muted" aria-hidden="true"></i>
|
||||
</p>
|
||||
<p class="lead text-center mx-4 mb-4">{{ "yourVaultIsLocked" | i18n }}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<div class="d-flex">
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
name="MasterPassword"
|
||||
class="text-monospace form-control"
|
||||
[(ngModel)]="masterPassword"
|
||||
required
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted form-text">
|
||||
{{ "loggedInAsEmailOn" | i18n: email : webVaultHostname }}
|
||||
</small>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-block btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span> <i class="bwi bwi-unlock" aria-hidden="true"></i> {{ "unlock" | i18n }} </span>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-block ml-2 mt-0"
|
||||
(click)="logOut()"
|
||||
>
|
||||
{{ "logOut" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form [bitSubmit]="submit" [formGroup]="formGroup">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
type="password"
|
||||
formControlName="masterPassword"
|
||||
bitInput
|
||||
appAutofocus
|
||||
name="masterPassword"
|
||||
required
|
||||
/>
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
<bit-hint>{{ "loggedInAsEmailOn" | i18n: email : webVaultHostname }}</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="tw-flex tw-gap-2">
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary" block>
|
||||
<i class="bwi bwi-unlock" aria-hidden="true"></i>
|
||||
{{ "unlock" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton bitFormButton block (click)="logOut()">
|
||||
{{ "logOut" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,18 +1,49 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Component, inject } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
|
||||
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
|
||||
|
||||
import { SharedModule } from "../shared";
|
||||
|
||||
@Component({
|
||||
selector: "app-lock",
|
||||
templateUrl: "lock.component.html",
|
||||
standalone: true,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class LockComponent extends BaseLockComponent {
|
||||
formBuilder = inject(FormBuilder);
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
masterPassword: ["", { validators: Validators.required, updateOn: "submit" }],
|
||||
});
|
||||
|
||||
get masterPasswordFormControl() {
|
||||
return this.formGroup.controls.masterPassword;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
|
||||
this.masterPasswordFormControl.setValue(this.masterPassword);
|
||||
|
||||
this.onSuccessfulSubmit = async () => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigateByUrl(this.successRoute);
|
||||
await this.router.navigateByUrl(this.successRoute);
|
||||
};
|
||||
}
|
||||
|
||||
async superSubmit() {
|
||||
await super.submit();
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.masterPassword = this.masterPasswordFormControl.value;
|
||||
await this.superSubmit();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
RegistrationStartComponent,
|
||||
RegistrationStartSecondaryComponent,
|
||||
RegistrationStartSecondaryComponentData,
|
||||
LockIcon,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
@@ -113,11 +114,6 @@ const routes: Routes = [
|
||||
component: SetPasswordComponent,
|
||||
data: { titleId: "setMasterPassword" } satisfies DataProperties,
|
||||
},
|
||||
{
|
||||
path: "lock",
|
||||
component: LockComponent,
|
||||
canActivate: [deepLinkGuard(), lockGuard()],
|
||||
},
|
||||
{ path: "verify-email", component: VerifyEmailTokenComponent },
|
||||
{
|
||||
path: "accept-organization",
|
||||
@@ -246,6 +242,21 @@ const routes: Routes = [
|
||||
pageTitle: "logIn",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "lock",
|
||||
canActivate: [deepLinkGuard(), lockGuard()],
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: LockComponent,
|
||||
},
|
||||
],
|
||||
data: {
|
||||
pageTitle: "yourVaultIsLockedV2",
|
||||
pageIcon: LockIcon,
|
||||
showReadonlyHostname: true,
|
||||
} satisfies AnonLayoutWrapperData,
|
||||
},
|
||||
{
|
||||
path: "2fa",
|
||||
canActivate: [unauthGuardFn()],
|
||||
|
||||
@@ -22,7 +22,6 @@ import { VerifyRecoverDeleteProviderComponent } from "../admin-console/providers
|
||||
import { SponsoredFamiliesComponent } from "../admin-console/settings/sponsored-families.component";
|
||||
import { SponsoringOrgRowComponent } from "../admin-console/settings/sponsoring-org-row.component";
|
||||
import { HintComponent } from "../auth/hint.component";
|
||||
import { LockComponent } from "../auth/lock.component";
|
||||
import { RecoverDeleteComponent } from "../auth/recover-delete.component";
|
||||
import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component";
|
||||
import { RegisterFormModule } from "../auth/register-form/register-form.module";
|
||||
@@ -141,7 +140,6 @@ import { SharedModule } from "./shared.module";
|
||||
FolderAddEditComponent,
|
||||
FrontendLayoutComponent,
|
||||
HintComponent,
|
||||
LockComponent,
|
||||
OrgAddEditComponent,
|
||||
OrgAttachmentsComponent,
|
||||
OrgCollectionsComponent,
|
||||
@@ -213,7 +211,6 @@ import { SharedModule } from "./shared.module";
|
||||
FolderAddEditComponent,
|
||||
FrontendLayoutComponent,
|
||||
HintComponent,
|
||||
LockComponent,
|
||||
OrgAddEditComponent,
|
||||
OrganizationLayoutComponent,
|
||||
OrgAttachmentsComponent,
|
||||
|
||||
@@ -854,8 +854,8 @@
|
||||
"emailAddress": {
|
||||
"message": "Email address"
|
||||
},
|
||||
"yourVaultIsLocked": {
|
||||
"message": "Your vault is locked. Verify your master password to continue."
|
||||
"yourVaultIsLockedV2": {
|
||||
"message": "Your vault is locked."
|
||||
},
|
||||
"uuid":{
|
||||
"message" : "UUID"
|
||||
@@ -8541,5 +8541,8 @@
|
||||
"contactBitwardenSupport": {
|
||||
"message": "contact Bitwarden support.",
|
||||
"description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'. 'Bitwarden' should not be translated"
|
||||
},
|
||||
"sponsored": {
|
||||
"message": "Sponsored"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user