1
0
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:
Daniel García
2024-07-10 11:28:58 +02:00
33 changed files with 930 additions and 87 deletions

View File

@@ -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"
},

View File

@@ -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>

View File

@@ -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 {}

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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>

View File

@@ -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();
};
}

View File

@@ -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()],

View File

@@ -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,

View File

@@ -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"
}
}