1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-04 18:53:20 +00:00

[PM-3810] Unify Passkeys view (#6335)

* Removed standalone fido2key view, update login view to show created date when a fido2key is present, reverted icon component to previous state without fido2key type, removed filters to handle standalone fido2key as login type

* Allow duplication

* Removed launchable behaviours from fido2 key view

* Reworked desktop views from standalone fido2keys to unified fido2keys in the login

* Reworked web views from standalone fido2keys to unified fido2keys in the login

* Fixed test case to not create standalone fido2keys

* Updated views to use fido2key creation date

* removed unused locale

* moved logic from template to class

* Removed fido2key ciphertype

* Removed fido2key ciphertype references
This commit is contained in:
SmithThe4th
2023-09-26 16:13:33 -04:00
committed by GitHub
parent 6fbc0c29f9
commit 9778cd73df
30 changed files with 64 additions and 516 deletions

View File

@@ -2425,15 +2425,9 @@
"application": {
"message": "Application"
},
"passkeyEditInformation": {
"message": "You cannot edit passkey application because it would invalidate the passkey."
},
"duplicatePasskey": {
"message": "A passkey with this ID already exists in this organization."
},
"passkeyTwoStepLogin": {
"message": "Available for two-step login"
},
"passkeyNotCopied": {
"message": "Passkey will not be copied"
},

View File

@@ -100,41 +100,3 @@
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</ng-container>
<ng-container *ngIf="cipher.type === cipherType.Fido2Key">
<button
type="button"
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'launch' | i18n }}"
(click)="launchCipher()"
*ngIf="!showView"
[ngClass]="{ disabled: !cipher.fido2Key.canLaunch }"
[attr.disabled]="!cipher.fido2Key.canLaunch ? '' : null"
>
<i class="bwi bwi-lg bwi-share-square" aria-hidden="true"></i>
</button>
<button
type="button"
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyUsername' | i18n }}"
(click)="copy(cipher, cipher.fido2Key.userDisplayName, 'username', 'Username')"
[ngClass]="{ disabled: !cipher.fido2Key.userDisplayName }"
[attr.disabled]="!cipher.fido2Key.userDisplayName ? '' : null"
>
<i class="bwi bwi-lg bwi-user" aria-hidden="true"></i>
</button>
<button
type="button"
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'loginPasskey' | i18n }}"
[ngClass]="{ disabled: true }"
disabled
>
<i class="bwi bwi-lg bwi-passkey" aria-hidden="true"></i>
</button>
</ng-container>

View File

@@ -21,9 +21,6 @@
<h2 class="box-header">
{{ "itemInformation" | i18n }}
</h2>
<div class="box-footer" *ngIf="cipher.type === cipherType.Fido2Key">
{{ "passkeyEditInformation" | i18n }}
</div>
<div class="box-content">
<div class="box-content-row" *ngIf="!editMode" appBoxRow>
<label for="type">{{ "type" | i18n }}</label>
@@ -138,7 +135,8 @@
<div class="box-content">
<div class="box-content-row text-muted">
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "passkeyTwoStepLogin" | i18n }}
{{ "dateCreated" | i18n }}
{{ cipher.login.fido2Keys[0].creationDate | date : "short" }}
</div>
</div>
</div>
@@ -475,39 +473,6 @@
/>
</div>
</div>
<!-- Fido2Key -->
<div *ngIf="cipher.type === cipherType.Fido2Key">
<div class="box-content-row" appBoxRow>
<label for="fido2KeyUserDisplayName">{{ "username" | i18n }}</label>
<input
id="fido2KeyUserDisplayName"
type="text"
name="Fido2Key.UserDisplayName"
[(ngModel)]="cipher.fido2Key.userDisplayName"
appInputVerbatim
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="box-content-row text-muted" appBoxRow>
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "dateCreated" | i18n }} {{ cipher.creationDate | date : "short" }}
</div>
<div class="box" *ngIf="cipher.fido2Key.canLaunch">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="fido2KeyApplication">{{ "application" | i18n }}</label>
<input
class="text-muted"
id="fido2KeyApplication"
type="text"
[value]="cipher.fido2Key.rpId"
readonly
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.type === cipherType.Login">

View File

@@ -70,9 +70,9 @@
<div class="icon"><i class="bwi bwi-fw bwi-lg bwi-globe"></i></div>
<span class="text">{{ "typeLogin" | i18n }}</span>
</div>
<span class="row-sub-label">{{
getTypeCountsSum(typeCounts, cipherType.Login, cipherType.Fido2Key)
}}</span>
<span class="row-sub-label">
{{ typeCounts.get(cipherType.Login) || 0 }}
</span>
<span><i class="bwi bwi-angle-right bwi-lg row-sub-icon"></i></span>
</button>
<button

View File

@@ -255,14 +255,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
}
async launchCipher(cipher: CipherView) {
let launchUri: string;
if (cipher.type === CipherType.Login && cipher.login.canLaunch) {
launchUri = cipher.login.launchUri;
} else if (cipher.type === CipherType.Fido2Key && cipher.fido2Key.canLaunch) {
launchUri = cipher.fido2Key.launchUri;
}
if (!launchUri) {
if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) {
return;
}
@@ -271,7 +264,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
}
this.preventSelected = true;
await this.cipherService.updateLastLaunchedDate(cipher.id);
BrowserApi.createNewTab(launchUri);
BrowserApi.createNewTab(cipher.login.launchUri);
if (this.popupUtils.inPopup(window)) {
BrowserApi.closePopup(window);
}
@@ -362,10 +355,6 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
this.collectionCounts = collectionCounts;
}
getTypeCountsSum(typeCounts: Map<CipherType, number>, ...types: CipherType[]): number {
return types.reduce((sum, type) => sum + (typeCounts.get(type) || 0), 0);
}
showSearching() {
return (
this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText))

View File

@@ -99,7 +99,6 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn
this.type = parseInt(params.type, null);
switch (this.type) {
case CipherType.Login:
case CipherType.Fido2Key:
this.groupingTitle = this.i18nService.t("logins");
break;
case CipherType.Card:
@@ -210,15 +209,7 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn
}
async launchCipher(cipher: CipherView) {
let launchUri: string;
if (cipher.type === CipherType.Login && cipher.login.canLaunch) {
launchUri = cipher.login.launchUri;
} else if (cipher.type === CipherType.Fido2Key && cipher.fido2Key.canLaunch) {
launchUri = cipher.fido2Key.launchUri;
}
if (!launchUri) {
if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) {
return;
}
@@ -227,7 +218,7 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn
}
this.preventSelected = true;
await this.cipherService.updateLastLaunchedDate(cipher.id);
BrowserApi.createNewTab(launchUri);
BrowserApi.createNewTab(cipher.login.launchUri);
if (this.popupUtils.inPopup(window)) {
BrowserApi.closePopup(window);
}
@@ -273,12 +264,7 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn
cipherPassesFilter = cipher.isDeleted;
}
if (this.type != null && cipherPassesFilter) {
//Fido2Key's should also be included in the Login type
if (this.type === CipherType.Login) {
cipherPassesFilter = cipher.type === this.type || cipher.type === CipherType.Fido2Key;
} else {
cipherPassesFilter = cipher.type === this.type;
}
cipherPassesFilter = cipher.type === this.type;
}
if (this.folderId != null && this.folderId != "none" && cipherPassesFilter) {
cipherPassesFilter = cipher.folderId === this.folderId;

View File

@@ -207,7 +207,8 @@
<div class="box-content">
<div class="box-content-row text-muted">
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "passkeyTwoStepLogin" | i18n }}
{{ "dateCreated" | i18n }}
{{ cipher.login.fido2Keys[0].creationDate | date : "short" }}
</div>
</div>
</div>
@@ -424,57 +425,6 @@
<div *ngIf="cipher.identity.country">{{ cipher.identity.country }}</div>
</div>
</div>
<!-- Fido2Key -->
<div *ngIf="cipher.type == cipherType.Fido2Key">
<div class="box-content-row">
<span class="row-label">{{ "username" | i18n }}</span>
{{ cipher.fido2Key.userDisplayName }}
</div>
<div class="box-content-row">
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "dateCreated" | i18n }} {{ cipher.creationDate | date : "short" }}
</div>
<div class="box" *ngIf="cipher.fido2Key.canLaunch">
<div class="box-content">
<div class="box-content-row box-content-row-flex">
<div class="row-main">
<label
class="row-label draggable"
draggable="true"
(dragstart)="setTextDataOnDrag($event, cipher.fido2Key.launchUri)"
>{{ "application" | i18n }}</label
>
<span title="{{ cipher.fido2Key.launchUri }}">
<input type="text" [value]="cipher.fido2Key.rpId" readonly aria-readonly="true" />
</span>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
(click)="launch(cipher.fido2Key)"
attr.aria-label="{{ 'launch' | i18n }} {{ cipher.fido2Key.launchUri }}"
[appA11yTitle]="'launch' | i18n"
>
<i class="bwi bwi-lg bwi-share-square" aria-hidden="true"></i>
</button>
<button
type="button"
class="row-btn"
appStopClick
attr.aria-label="{{ 'copyUri' | i18n }} {{ cipher.fido2Key.launchUri }}"
[appA11yTitle]="'copyUri' | i18n"
(click)="copy(cipher.fido2Key.launchUri, 'uri', 'URI')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.login && cipher.login.hasUris">
@@ -619,8 +569,7 @@
*ngIf="
cipher.type !== cipherType.SecureNote &&
!cipher.isDeleted &&
(!this.inPopout || this.loadAction) &&
cipher.type != cipherType.Fido2Key
(!this.inPopout || this.loadAction)
"
>
<div class="row-main text-primary">
@@ -649,7 +598,7 @@
class="box-content-row"
appStopClick
(click)="clone()"
*ngIf="!cipher.organizationId && !cipher.isDeleted && cipher.type != cipherType.Fido2Key"
*ngIf="!cipher.organizationId && !cipher.isDeleted"
>
<div class="row-main text-primary">
<div class="icon text-primary" aria-hidden="true">

View File

@@ -2422,15 +2422,9 @@
"application": {
"message": "Application"
},
"passkeyEditInformation": {
"message": "You cannot edit passkey application because it would invalidate the passkey."
},
"duplicatePasskey": {
"message": "A passkey with this ID already exists in this organization."
},
"passkeyTwoStepLogin": {
"message": "Available for two-step login"
},
"passkeyNotCopied": {
"message": "Passkey will not be copied"
},

View File

@@ -121,7 +121,8 @@
appBoxRow
>
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "passkeyTwoStepLogin" | i18n }}
{{ "dateCreated" | i18n }}
{{ cipher.login.fido2Keys[0].creationDate | date : "short" }}
</div>
<div class="box-content-row" appBoxRow>
@@ -458,24 +459,6 @@
/>
</div>
</div>
<!-- Fido2Key -->
<div *ngIf="cipher.type === cipherType.Fido2Key">
<div class="box-content-row" appBoxRow>
<label for="fido2KeyUserDisplayName">{{ "username" | i18n }}</label>
<input
id="fido2KeyUserDisplayName"
type="text"
name="Fido2Key.UserDisplayName"
[(ngModel)]="cipher.fido2Key.userDisplayName"
appInputVerbatim
[readonly]="!cipher.edit && editMode"
/>
</div>
<div class="box-content-row text-muted" appBoxRow>
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "dateCreated" | i18n }} {{ cipher.creationDate | date : "short" }}
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.type === cipherType.Login">
@@ -556,24 +539,6 @@
</button>
</div>
</div>
<!-- Fido2Key Application -->
<div class="box" *ngIf="cipher.type === cipherType.Fido2Key && cipher.fido2Key.canLaunch">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="fido2KeyApplication">{{ "application" | i18n }}</label>
<input
class="text-muted"
id="fido2KeyApplication"
type="text"
[value]="cipher.fido2Key.rpId"
readonly
/>
</div>
</div>
<div class="box-footer">
{{ "passkeyEditInformation" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>

View File

@@ -286,7 +286,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.editCipher(cipher);
}),
});
if (!cipher.organizationId && !cipher.fido2Key?.rpId) {
if (!cipher.organizationId) {
menu.push({
label: this.i18nService.t("clone"),
click: () =>
@@ -357,14 +357,6 @@ export class VaultComponent implements OnInit, OnDestroy {
});
}
break;
case CipherType.Fido2Key:
if (cipher.fido2Key.canLaunch) {
menu.push({
label: this.i18nService.t("launch"),
click: () => this.platformUtilsService.launchUri(cipher.fido2Key.launchUri),
});
}
break;
default:
break;
}

View File

@@ -121,7 +121,7 @@
<!--Passkey-->
<div class="box-content-row text-muted" *ngIf="cipher.login.fido2Keys[0]">
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "passkeyTwoStepLogin" | i18n }}
{{ "dateCreated" | i18n }} {{ cipher.login.fido2Keys[0].creationDate | date : "short" }}
</div>
<div
class="box-content-row box-content-row-flex totp"
@@ -394,19 +394,6 @@
<div *ngIf="cipher.identity.country">{{ cipher.identity.country }}</div>
</div>
</div>
<!-- Fido2Key -->
<div *ngIf="cipher.type == cipherType.Fido2Key">
<div class="box-content-row">
<div class="row-main">
<span class="row-label">{{ "username" | i18n }}</span>
{{ cipher.fido2Key.userDisplayName }}
</div>
</div>
<div class="box-content-row">
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "dateCreated" | i18n }} {{ cipher.creationDate | date : "short" }}
</div>
</div>
</div>
</div>
<div class="box" *ngIf="cipher.login && cipher.login.hasUris">
@@ -483,43 +470,6 @@
<div class="box-content-row pre-wrap">{{ cipher.notes }}</div>
</div>
</div>
<div class="box" *ngIf="cipher.type == cipherType.Fido2Key && cipher.fido2Key.canLaunch">
<div class="box-content">
<div class="box-content-row box-content-row-flex">
<div class="row-main">
<label
class="row-label draggable"
draggable="true"
(dragstart)="setTextDataOnDrag($event, cipher.fido2Key.launchUri)"
>{{ "application" | i18n }}</label
>
<span title="{{ cipher.fido2Key.launchUri }}">
<input type="text" [value]="cipher.fido2Key.rpId" readonly aria-readonly="true" />
</span>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
(click)="launch(cipher.fido2Key)"
[appA11yTitle]="'launch' | i18n"
>
<i class="bwi bwi-lg bwi-share-square" aria-hidden="true"></i>
</button>
<button
type="button"
class="row-btn"
appStopClick
[appA11yTitle]="'copyUri' | i18n"
(click)="copy(cipher.fido2Key.launchUri, 'uri', 'URI')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
<app-vault-view-custom-fields
*ngIf="cipher.hasFields"
[cipher]="cipher"
@@ -605,7 +555,7 @@
<button
type="button"
class="primary"
*ngIf="!cipher?.organizationId && !cipher.isDeleted && !cipher.fido2Key?.rpId"
*ngIf="!cipher?.organizationId && !cipher.isDeleted"
(click)="clone()"
appA11yTitle="{{ 'clone' | i18n }}"
>

View File

@@ -1,3 +1,4 @@
import { DatePipe } from "@angular/common";
import { Component } from "@angular/core";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
@@ -47,7 +48,8 @@ export class EmergencyAddEditComponent extends BaseAddEditComponent {
organizationService: OrganizationService,
logService: LogService,
sendApiService: SendApiService,
dialogService: DialogService
dialogService: DialogService,
datePipe: DatePipe
) {
super(
cipherService,
@@ -66,7 +68,8 @@ export class EmergencyAddEditComponent extends BaseAddEditComponent {
logService,
passwordRepromptService,
sendApiService,
dialogService
dialogService,
datePipe
);
}

View File

@@ -99,19 +99,6 @@
{{ "launch" | i18n }}
</a>
</ng-container>
<!-- Fido2Key -->
<ng-container *ngIf="cipher.type === CipherType.Fido2Key && !cipher.isDeleted">
<a
bitMenuItem
*ngIf="cipher.fido2Key.canLaunch"
type="button"
[href]="cipher.fido2Key.launchUri"
target="_blank"
>
<i class="bwi bwi-fw bwi-share-square" aria-hidden="true"></i>
{{ "launch" | i18n }}
</a>
</ng-container>
<button bitMenuItem type="button" (click)="attachments()">
<i class="bwi bwi-fw bwi-paperclip" aria-hidden="true"></i>

View File

@@ -166,9 +166,8 @@ export class VaultItemsComponent {
protected canClone(vaultItem: VaultItem) {
return (
((vaultItem.cipher.organizationId && this.cloneableOrganizationCiphers) ||
vaultItem.cipher.organizationId == null) &&
!vaultItem.cipher.fido2Key?.rpId
(vaultItem.cipher.organizationId && this.cloneableOrganizationCiphers) ||
vaultItem.cipher.organizationId == null
);
}

View File

@@ -201,7 +201,7 @@
class="form-control"
type="text"
name="Login.Fido2key"
[value]="'passkeyTwoStepLogin' | i18n"
[value]="fido2KeyCreationDateValue"
appInputVerbatim
disabled
readonly
@@ -832,89 +832,6 @@
</div>
</div>
</ng-container>
<!-- Fido2Key -->
<ng-container *ngIf="cipher.type === cipherType.Fido2Key">
<div class="row">
<div class="col-6 form-group">
<label for="fido2KeyUserDisplayName">{{ "username" | i18n }}</label>
<div class="input-group">
<input
id="fido2KeyUserDisplayName"
class="form-control"
type="text"
name="Fido2key.UserDisplayName"
[(ngModel)]="cipher.fido2Key.userDisplayName"
appInputVerbatim
[disabled]="cipher.isDeleted || viewOnly"
[readonly]="!cipher.edit && editMode"
/>
<div class="input-group-append" *ngIf="!cipher.isDeleted">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyUsername' | i18n }}"
(click)="copy(cipher.fido2Key.userDisplayName, 'username', 'Username')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="col-6 form-group">
<label for="fido2keyCreationDate">{{ "typePasskey" | i18n }}</label>
<div class="input-group">
<input
id="fido2keyCreationDate"
class="form-control"
type="text"
name="Fido2key.CreationDate"
[value]="('dateCreated' | i18n) + ' ' + (cipher.creationDate | date : 'short')"
appInputVerbatim
disabled
readonly
/>
</div>
</div>
</div>
<div class="row">
<div class="col-12 form-group">
<label for="fido2keyApplication">{{ "application" | i18n }}</label>
<div class="input-group">
<input
class="form-control"
id="fido2keyApplication"
type="text"
name="Fido2key.Application"
[value]="cipher.fido2Key.rpId"
disabled
readonly
appInputVerbatim
/>
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'launch' | i18n }}"
(click)="launch(cipher.fido2Key)"
>
<i class="bwi bwi-lg bwi-share-square" aria-hidden="true"></i>
</button>
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyUri' | i18n }}"
(click)="copy(cipher.fido2Key.launchUri, 'uri', 'URI')"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
</div>
</div>
<small class="form-text text-muted">{{ "passkeyEditInformation" | i18n }}</small>
</div>
</div>
</ng-container>
<div class="form-group">
<label for="notes">{{ "notes" | i18n }}</label>
<textarea

View File

@@ -1,3 +1,4 @@
import { DatePipe } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component";
@@ -42,6 +43,15 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
protected totpInterval: number;
protected override componentName = "app-vault-add-edit";
get fido2KeyCreationDateValue(): string {
const dateCreated = this.i18nService.t("dateCreated");
const creationDate = this.datePipe.transform(
this.cipher?.login?.fido2Keys?.[0]?.creationDate,
"short"
);
return `${dateCreated} ${creationDate}`;
}
constructor(
cipherService: CipherService,
folderService: FolderService,
@@ -59,7 +69,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
logService: LogService,
passwordRepromptService: PasswordRepromptService,
sendApiService: SendApiService,
dialogService: DialogService
dialogService: DialogService,
private datePipe: DatePipe
) {
super(
cipherService,

View File

@@ -211,17 +211,6 @@ describe("createFilter", () => {
expect(result).toBe(true);
});
});
describe("given a cipher with Fido2Key type", () => {
it("should return true when filter is login", () => {
const cipher = createCipher({ type: CipherType.Fido2Key });
const filterFunction = createFilterFunction({ type: "login" });
const result = filterFunction(cipher);
expect(result).toBe(true);
});
});
});
function createCipher(options: Partial<CipherView> = {}) {

View File

@@ -16,12 +16,7 @@ export function createFilterFunction(filter: RoutedVaultFilterModel): FilterFunc
if (filter.type === "identity" && cipher.type !== CipherType.Identity) {
return false;
}
// Login and Fido2Key are both login types
if (
filter.type === "login" &&
cipher.type !== CipherType.Login &&
cipher.type !== CipherType.Fido2Key
) {
if (filter.type === "login" && cipher.type !== CipherType.Login) {
return false;
}
if (filter.type === "note" && cipher.type !== CipherType.SecureNote) {

View File

@@ -42,15 +42,6 @@ describe("VaultFilter", () => {
},
null
);
const loginCiphersFilter = new TreeNode<CipherTypeFilter>(
{
id: "login",
name: "login",
type: CipherType.Login,
icon: "bwi-globe",
},
null
);
const trashFilter = new TreeNode<CipherTypeFilter>(
{
id: "trash",
@@ -288,19 +279,6 @@ describe("VaultFilter", () => {
expect(result).toBe(true);
});
});
describe("given a cipher with Fido2Key type", () => {
it("should return true when filter is login", () => {
const cipher = createCipher({ type: CipherType.Fido2Key });
const filterFunction = createFilterFunction({
selectedCipherTypeNode: loginCiphersFilter,
});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
});
});
});

View File

@@ -120,13 +120,7 @@ export class VaultFilter {
cipherPassesFilter = cipher.isDeleted;
}
if (this.cipherType && cipherPassesFilter) {
//Fido2Key's should also be included in the Login type
if (this.cipherType === CipherType.Login) {
cipherPassesFilter =
cipher.type === this.cipherType || cipher.type === CipherType.Fido2Key;
} else {
cipherPassesFilter = cipher.type === this.cipherType;
}
cipherPassesFilter = cipher.type === this.cipherType;
}
if (this.selectedFolderNode) {
// No folder

View File

@@ -1,3 +1,4 @@
import { DatePipe } from "@angular/common";
import { Component } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -49,7 +50,8 @@ export class AddEditComponent extends BaseAddEditComponent {
passwordRepromptService: PasswordRepromptService,
organizationService: OrganizationService,
sendApiService: SendApiService,
dialogService: DialogService
dialogService: DialogService,
datePipe: DatePipe
) {
super(
cipherService,
@@ -68,7 +70,8 @@ export class AddEditComponent extends BaseAddEditComponent {
logService,
passwordRepromptService,
sendApiService,
dialogService
dialogService,
datePipe
);
}

View File

@@ -7178,15 +7178,9 @@
"application": {
"message": "Application"
},
"passkeyEditInformation": {
"message": "You cannot edit passkey application because it would invalidate the passkey."
},
"duplicatePasskey": {
"message": "A passkey with this ID already exists in this organization."
},
"passkeyTwoStepLogin": {
"message": "Available for two-step login"
},
"passkeyNotCopied": {
"message": "Passkey will not be copied"
},

View File

@@ -11,7 +11,6 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { Checkable, isChecked } from "@bitwarden/common/types/checkable";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@@ -100,15 +99,6 @@ export class ShareComponent implements OnInit, OnDestroy {
const orgName =
orgs.find((o) => o.id === this.organizationId)?.name ?? this.i18nService.t("organization");
if (await this.checkFido2KeyExistsInOrg(cipherView, this.organizationId)) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("duplicatePasskey")
);
return;
}
try {
this.formPromise = this.cipherService
.shareWithServer(cipherView, this.organizationId, selectedCollectionIds)
@@ -138,26 +128,4 @@ export class ShareComponent implements OnInit, OnDestroy {
}
return false;
}
private async checkFido2KeyExistsInOrg(cipher: CipherView, orgId: string): Promise<boolean> {
if (cipher.type === CipherType.Fido2Key || cipher.login?.fido2Keys[0]) {
//Determine if Fido2Key object is disvoverable or non discoverable
const newFido2Key = cipher.login?.fido2Keys[0] || cipher.fido2Key;
const ciphers = await this.cipherService.getAllDecrypted();
const exisitingOrgCiphers = ciphers.filter((c) => c.organizationId === orgId);
return exisitingOrgCiphers.some((c) => {
const existingFido2key = c.login?.fido2Keys[0] || c.fido2Key;
return (
!c.isDeleted &&
existingFido2key.rpId === newFido2Key.rpId &&
existingFido2key.userHandle === newFido2Key.userHandle
);
});
}
return false;
}
}

View File

@@ -70,34 +70,33 @@ export class IconComponent implements OnInit {
switch (cipher.type) {
case CipherType.Login:
case CipherType.Fido2Key: {
icon = cipher.type === CipherType.Login ? "bwi-globe" : "bwi-passkey";
icon = "bwi-globe";
let uri =
cipher.type === CipherType.Login ? cipher.login.uri : cipher.fido2Key.launchUri;
let isWebsite = false;
if (cipher.login.uri) {
let hostnameUri = cipher.login.uri;
let isWebsite = false;
if (uri) {
if (uri.indexOf("androidapp://") === 0) {
if (hostnameUri.indexOf("androidapp://") === 0) {
icon = "bwi-android";
image = null;
} else if (uri.indexOf("iosapp://") === 0) {
} else if (hostnameUri.indexOf("iosapp://") === 0) {
icon = "bwi-apple";
image = null;
} else if (imageEnabled && uri.indexOf("://") === -1 && uri.indexOf(".") > -1) {
uri = "http://" + uri;
} else if (
imageEnabled &&
hostnameUri.indexOf("://") === -1 &&
hostnameUri.indexOf(".") > -1
) {
hostnameUri = "http://" + hostnameUri;
isWebsite = true;
} else if (imageEnabled) {
isWebsite = uri.indexOf("http") === 0 && uri.indexOf(".") > -1;
isWebsite = hostnameUri.indexOf("http") === 0 && hostnameUri.indexOf(".") > -1;
}
if (imageEnabled && isWebsite) {
try {
image = iconsUrl + "/" + Utils.getHostname(uri) + "/icon.png";
fallbackImage =
cipher.type === CipherType.Login
? "images/bwi-globe.png"
: "images/bwi-passkey.png";
image = iconsUrl + "/" + Utils.getHostname(hostnameUri) + "/icon.png";
fallbackImage = "images/bwi-globe.png";
} catch (e) {
// Ignore error since the fallback icon will be shown if image is null.
}
@@ -106,7 +105,6 @@ export class IconComponent implements OnInit {
image = null;
}
break;
}
case CipherType.SecureNote:
icon = "bwi-sticky-note";
break;

View File

@@ -216,19 +216,6 @@ describe("VaultFilter", () => {
expect(result).toBe(true);
});
});
describe("given a cipher with Fido2Key type", () => {
it("should return true when filter is login", () => {
const cipher = createCipher({ type: CipherType.Fido2Key });
const filterFunction = createFilterFunction({
cipherType: CipherType.Fido2Key,
});
const result = filterFunction(cipher);
expect(result).toBe(true);
});
});
});
});

View File

@@ -45,13 +45,7 @@ export class VaultFilter {
cipherPassesFilter = cipher.isDeleted;
}
if (this.cipherType != null && cipherPassesFilter) {
// Fido2Key's should also be included in the Login type
if (this.cipherType === CipherType.Login) {
cipherPassesFilter =
cipher.type === this.cipherType || cipher.type === CipherType.Fido2Key;
} else {
cipherPassesFilter = cipher.type === this.cipherType;
}
cipherPassesFilter = cipher.type === this.cipherType;
}
if (this.selectedFolder && this.selectedFolderId == null && cipherPassesFilter) {
cipherPassesFilter = cipher.folderId == null;

View File

@@ -3,5 +3,4 @@ export enum CipherType {
SecureNote = 2,
Card = 3,
Identity = 4,
Fido2Key = 5,
}

View File

@@ -11,7 +11,6 @@ import { Cipher } from "../domain/cipher";
import { AttachmentView } from "./attachment.view";
import { CardView } from "./card.view";
import { Fido2KeyView } from "./fido2-key.view";
import { FieldView } from "./field.view";
import { IdentityView } from "./identity.view";
import { LoginView } from "./login.view";
@@ -36,7 +35,6 @@ export class CipherView implements View, InitializerMetadata {
identity = new IdentityView();
card = new CardView();
secureNote = new SecureNoteView();
fido2Key = new Fido2KeyView();
attachments: AttachmentView[] = null;
fields: FieldView[] = null;
passwordHistory: PasswordHistoryView[] = null;

View File

@@ -20,14 +20,6 @@ export class Fido2KeyView extends ItemView {
return this.userDisplayName;
}
get canLaunch(): boolean {
return this.rpId != null;
}
get launchUri(): string {
return this.canLaunch ? `https://${this.rpId}` : null;
}
static fromJSON(obj: Partial<Jsonify<Fido2KeyView>>): Fido2KeyView {
const creationDate = obj.creationDate != null ? new Date(obj.creationDate) : null;
return Object.assign(new Fido2KeyView(), obj, {

View File

@@ -760,12 +760,8 @@ function createCipherView(
fido2KeyView.keyValue ??
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTC-7XDZipXbaVBlnkjlBgO16ZmqBZWejK2iYo6lV0dehRANCAASOcM2WduNq1DriRYN7ZekvZz-bRhA-qNT4v0fbp5suUFJyWmgOQ0bybZcLXHaerK5Ep1JiSrQcewtQNgLtry7f";
if (cipher.type === CipherType.Login) {
cipher.login = new LoginView();
cipher.login.fido2Keys = [fido2KeyView];
} else {
cipher.fido2Key = fido2KeyView;
}
cipher.login = new LoginView();
cipher.login.fido2Keys = [fido2KeyView];
return cipher;
}