1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 22:33:35 +00:00

[PM-1976] Display passkeys properly on the browser (#5616)

* Removed passkeys from the vault types filter and added fucntion to get the count of Fido2keys and Login types

* Updated build filter to take Fido2key type as a Login type

* Updated icon font files

* Updated vault items and view to handle changes with fido2keys

* Updated add edit view for fido2keys

* Prevent moving passkeys to an organization where it exists

* Prevent moving passkeys to an organization where it exists

* Added view for non-discoverable passkeys

* Added diaglog to inform user that passkey won't be copied when cloning a non discoverable key

* Muted text that shows cipher item is available for 2fa

* Changed conditional to check if an organization already has the same passkey item

* Muted text to align with figma designs and used rpId for the application input value

* Modified checkFido2KeyExistsInOrg function to workk with discoverable and non discoverable keys

* Differentiate between non-discoverable and discoverable keys when moving to an organization

* Added suggested changes from PR review

* Updated font files css changes

* Fixed bug preventing launch bitton from working for Login types (#5639)

* [PM-1574] Display passkeys on web (#5651)

* Allowed discoverable Fido2key type to be displayed alongside Login type

* Added view during edit for discoverable and non-discoverable passkeys

* Fixed PR comments, added relvant tests to domain changes

* Fixed imports and updated the launch function to use the Launchable interface

* Added launch on vault filter for fido2key types

* Added missing passkey text field in edit view (#5800)

* [PM-1977] Display passkeys properly on the desktop (#5763)

* Allowed discoverable Fido2key type to be displayed alongside Login type

* Added view during edit for discoverable and non-discoverable passkeys

* Fixed PR comments, added relvant tests to domain changes

* Fixed imports and updated the launch function to use the Launchable interface

* Added fido2key to login filter and added view display for fido2key

* Added passkeys view for non discoverable passkeys and edit view for passkeys

* Fixed PR comments

* switched date format to short

* [PM-3046] [PM-3047] Defects for discoverable and non-discoverable passkeys on desktop and web (#5847)

* Added missing passkey text field in edit view (#5800)

* Added dialog to clone no discoverable passkeys on web and desktop.Also, removed clone on the desktop for discoverable passkeys and added passkey view to non- discoverable passkeys on desktop during edit

* Prevent cloning dialog on non fido2key ciphers

* Made fido2key use website favicon if avaialble instead of the passkey icon

* Do not display passkey view on clone edit for dekstop

* Do not display passkey view on clone edit for browser

* Prevented movement of passkeys ND or D to an organization once one exists and also made it possible for org memebers with user roles to move passkeys to an organization. (#5868)

* two step passkey view was outside the conditional (#5872)
This commit is contained in:
SmithThe4th
2023-07-24 09:34:10 -04:00
committed by GitHub
parent ca5b033cfd
commit 31276a92f8
36 changed files with 644 additions and 76 deletions

View File

@@ -2249,5 +2249,26 @@
},
"accessDenied": {
"message": "Access denied. You do not have permission to view this page."
},
"loginPasskey": {
"message": "This login uses a passkey"
},
"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"
},
"passkeyNotCopiedAlert": {
"message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?"
}
}

View File

@@ -100,3 +100,41 @@
<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.userName, 'username', 'Username')"
[ngClass]="{ disabled: !cipher.fido2Key.userName }"
[attr.disabled]="!cipher.fido2Key.userName ? '' : 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,6 +21,9 @@
<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>
@@ -129,6 +132,17 @@
</button>
</div>
</div>
<!--Passkey-->
<div class="box" *ngIf="cipher.login.fido2Key && !cloneMode">
<div class="box-content">
<div class="box-content-row text-muted">
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "passkeyTwoStepLogin" | i18n }}
</div>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label>
<input
@@ -461,6 +475,39 @@
/>
</div>
</div>
<!-- Fido2Key -->
<div *ngIf="cipher.type === cipherType.Fido2Key">
<div class="box-content-row" appBoxRow>
<label for="fido2KeyUsername">{{ "username" | i18n }}</label>
<input
id="fido2KeyUsername"
type="text"
name="Fido2Key.Username"
[(ngModel)]="cipher.fido2Key.userName"
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,7 +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">{{ typeCounts.get(cipherType.Login) || 0 }}</span>
<span class="row-sub-label">{{
getTypeCountsSum(typeCounts, cipherType.Login, cipherType.Fido2Key)
}}</span>
<span><i class="bwi bwi-angle-right bwi-lg row-sub-icon"></i></span>
</button>
<button
@@ -112,19 +114,6 @@
<span class="row-sub-label">{{ typeCounts.get(cipherType.SecureNote) || 0 }}</span>
<span><i class="bwi bwi-angle-right bwi-lg row-sub-icon"></i></span>
</button>
<button
type="button"
class="box-content-row"
appStopClick
(click)="selectType(cipherType.Fido2Key)"
>
<div class="row-main">
<div class="icon"><i class="bwi bwi-fw bwi-lg bwi-key"></i></div>
<span class="text">{{ "typePasskey" | i18n }}</span>
</div>
<span class="row-sub-label">{{ typeCounts.get(cipherType.Fido2Key) || 0 }}</span>
<span><i class="bwi bwi-angle-right bwi-lg row-sub-icon"></i></span>
</button>
</div>
</div>
<div class="box list" *ngIf="nestedFolders?.length">

View File

@@ -255,7 +255,14 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
}
async launchCipher(cipher: CipherView) {
if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) {
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) {
return;
}
@@ -264,7 +271,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
}
this.preventSelected = true;
await this.cipherService.updateLastLaunchedDate(cipher.id);
BrowserApi.createNewTab(cipher.login.launchUri);
BrowserApi.createNewTab(launchUri);
if (this.popupUtils.inPopup(window)) {
BrowserApi.closePopup(window);
}
@@ -355,6 +362,10 @@ 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,6 +99,7 @@ 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:
@@ -209,7 +210,15 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn
}
async launchCipher(cipher: CipherView) {
if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) {
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) {
return;
}
@@ -218,7 +227,7 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn
}
this.preventSelected = true;
await this.cipherService.updateLastLaunchedDate(cipher.id);
BrowserApi.createNewTab(cipher.login.launchUri);
BrowserApi.createNewTab(launchUri);
if (this.popupUtils.inPopup(window)) {
BrowserApi.closePopup(window);
}
@@ -264,7 +273,12 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn
cipherPassesFilter = cipher.isDeleted;
}
if (this.type != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.type;
//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;
}
}
if (this.folderId != null && this.folderId != "none" && cipherPassesFilter) {
cipherPassesFilter = cipher.folderId === this.folderId;

View File

@@ -201,6 +201,16 @@
</span>
</div>
</div>
<!--Passkey-->
<div class="box" *ngIf="cipher.login.fido2Key">
<div class="box-content">
<div class="box-content-row text-muted">
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "passkeyTwoStepLogin" | i18n }}
</div>
</div>
</div>
</div>
<!-- Card -->
<div *ngIf="cipher.card">
@@ -416,25 +426,54 @@
</div>
<!-- Fido2Key -->
<div *ngIf="cipher.type == cipherType.Fido2Key">
<div class="box-content-row">
<span class="row-label">{{ "keyType" | i18n }}</span>
{{ cipher.fido2Key.keyType }}
</div>
<div class="box-content-row">
<span class="row-label">{{ "keyCurve" | i18n }}</span>
{{ cipher.fido2Key.keyCurve }}
</div>
<div class="box-content-row">
<span class="row-label">{{ "rpName" | i18n }}</span>
{{ cipher.fido2Key.rpName }}
</div>
<div class="box-content-row">
<span class="row-label">{{ "username" | i18n }}</span>
{{ cipher.fido2Key.userName }}
</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>

View File

@@ -21,7 +21,6 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
@@ -41,7 +40,6 @@ export class ViewComponent extends BaseViewComponent {
tab: any;
loadPageDetailsTimeout: number;
inPopout = false;
cipherType = CipherType;
constructor(
cipherService: CipherService,

View File

@@ -2268,5 +2268,26 @@
},
"accessDenied": {
"message": "Access denied. You do not have permission to view this page."
},
"typePasskey": {
"message": "Passkey"
},
"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"
},
"passkeyNotCopiedAlert": {
"message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?"
}
}

View File

@@ -114,6 +114,16 @@
</button>
</div>
</div>
<!--Passkey-->
<div
class="box-content-row text-muted"
*ngIf="cipher.login.fido2Key && !cloneMode"
appBoxRow
>
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "passkeyTwoStepLogin" | i18n }}
</div>
<div class="box-content-row" appBoxRow>
<label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label>
<input
@@ -448,6 +458,24 @@
/>
</div>
</div>
<!-- Fido2Key -->
<div *ngIf="cipher.type === cipherType.Fido2Key">
<div class="box-content-row" appBoxRow>
<label for="fido2KeyUsername">{{ "username" | i18n }}</label>
<input
id="fido2KeyUsername"
type="text"
name="Fido2Key.Username"
[(ngModel)]="cipher.fido2Key.userName"
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">
@@ -528,6 +556,24 @@
</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

@@ -288,7 +288,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.editCipher(cipher);
}),
});
if (!cipher.organizationId) {
if (!cipher.organizationId && !cipher.fido2Key?.rpId) {
menu.push({
label: this.i18nService.t("clone"),
click: () =>
@@ -359,6 +359,14 @@ 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

@@ -118,6 +118,11 @@
</button>
</div>
</div>
<!--Passkey-->
<div class="box-content-row text-muted" *ngIf="cipher.login.fido2Key">
<span class="row-label">{{ "typePasskey" | i18n }}</span>
{{ "passkeyTwoStepLogin" | i18n }}
</div>
<div
class="box-content-row box-content-row-flex totp"
[ngClass]="{ low: totpLow }"
@@ -389,6 +394,19 @@
<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.userName }}
</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">
@@ -465,6 +483,43 @@
<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"
@@ -550,7 +605,7 @@
<button
type="button"
class="primary"
*ngIf="!cipher?.organizationId && !cipher.isDeleted"
*ngIf="!cipher?.organizationId && !cipher.isDeleted && !cipher.fido2Key?.rpId"
(click)="clone()"
appA11yTitle="{{ 'clone' | i18n }}"
>

View File

@@ -103,6 +103,20 @@
{{ "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>
{{ "attachments" | i18n }}

View File

@@ -89,10 +89,7 @@
[showGroups]="showGroups"
[showPremiumFeatures]="showPremiumFeatures"
[useEvents]="useEvents"
[cloneable]="
(item.cipher.organizationId && cloneableOrganizationCiphers) ||
item.cipher.organizationId == null
"
[cloneable]="canClone(item)"
[organizations]="allOrganizations"
[collections]="allCollections"
[checked]="selection.isSelected(item)"

View File

@@ -164,6 +164,14 @@ export class VaultItemsComponent {
});
}
protected canClone(vaultItem: VaultItem) {
return (
((vaultItem.cipher.organizationId && this.cloneableOrganizationCiphers) ||
vaultItem.cipher.organizationId == null) &&
!vaultItem.cipher.fido2Key?.rpId
);
}
private refreshItems() {
const collections: VaultItem[] = this.collections.map((collection) => ({ collection }));
const ciphers: VaultItem[] = this.ciphers.map((cipher) => ({ cipher }));

View File

@@ -191,6 +191,25 @@
</button>
</div>
</div>
<ng-container *ngIf="cipher.login.fido2Key">
<div class="row">
<div class="col-6 form-group">
<label for="loginFido2key">{{ "typePasskey" | i18n }}</label>
<div class="input-group">
<input
id="loginFido2key"
class="form-control"
type="text"
name="Login.Fido2key"
[value]="'passkeyTwoStepLogin' | i18n"
appInputVerbatim
disabled
readonly
/>
</div>
</div>
</div>
</ng-container>
<div class="tw-flex tw-flex-row">
<div class="tw-mb-4 tw-w-1/2">
<label for="loginTotp">{{ "authenticatorKeyTotp" | i18n }}</label>
@@ -813,6 +832,89 @@
</div>
</div>
</ng-container>
<!-- Fido2Key -->
<ng-container *ngIf="cipher.type === cipherType.Fido2Key">
<div class="row">
<div class="col-6 form-group">
<label for="fido2keyUsername">{{ "username" | i18n }}</label>
<div class="input-group">
<input
id="fido2keyUsername"
class="form-control"
type="text"
name="Fido2key.Username"
[(ngModel)]="cipher.fido2Key.userName"
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.userName, '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

@@ -20,7 +20,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 { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
import { Launchable } from "@bitwarden/common/vault/interfaces/launchable";
@Component({
selector: "app-vault-add-edit",
@@ -131,7 +131,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
}
}
launch(uri: LoginUriView) {
launch(uri: Launchable) {
if (!uri.canLaunch) {
return;
}

View File

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

View File

@@ -42,6 +42,15 @@ 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",
@@ -279,6 +288,19 @@ 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,7 +120,13 @@ export class VaultFilter {
cipherPassesFilter = cipher.isDeleted;
}
if (this.cipherType && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.cipherType;
//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;
}
}
if (this.selectedFolderNode) {
// No folder

View File

@@ -641,6 +641,18 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async cloneCipher(cipher: CipherView) {
if (cipher.login?.fido2Key) {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "passkeyNotCopied" },
content: { key: "passkeyNotCopiedAlert" },
type: SimpleDialogType.INFO,
});
if (!confirmed) {
return false;
}
}
const component = await this.editCipher(cipher);
component.cloneMode = true;
}

View File

@@ -6844,5 +6844,26 @@
},
"updatedTempPassword": {
"message": "User updated a password issued through account recovery."
},
"typePasskey": {
"message": "Passkey"
},
"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"
},
"passkeyNotCopiedAlert": {
"message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?"
}
}

View File

@@ -11,6 +11,7 @@ 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";
@@ -99,6 +100,15 @@ 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)
@@ -128,4 +138,26 @@ export class ShareComponent implements OnInit, OnDestroy {
}
return false;
}
private async checkFido2KeyExistsInOrg(cipher: CipherView, orgId: string): Promise<boolean> {
if (cipher.type === CipherType.Fido2Key || cipher.login?.fido2Key) {
//Determine if Fido2Key object is disvoverable or non discoverable
const newFido2Key = cipher.login?.fido2Key || cipher.fido2Key;
const ciphers = await this.cipherService.getAllDecrypted();
const exisitingOrgCiphers = ciphers.filter((c) => c.organizationId === orgId);
return exisitingOrgCiphers.some((c) => {
const existingFido2key = c.login?.fido2Key || c.fido2Key;
return (
!c.isDeleted &&
existingFido2key.rpId === newFido2Key.rpId &&
existingFido2key.userHandle === newFido2Key.userHandle
);
});
}
return false;
}
}

View File

@@ -53,7 +53,7 @@
<glyph unicode="&#xe918;" glyph-name="plus" data-tags="bw-plus" d="M992 420.864h-416c-8.486 0-16.627 3.372-22.63 9.373-5.997 6.001-9.37 14.14-9.37 22.627v411.136c0 8.487-3.373 16.626-9.37 22.627-6.003 6.001-14.144 9.373-22.63 9.373s-16.627-3.371-22.627-9.373c-6.001-6.001-9.373-14.14-9.373-22.627v-411.136c0-8.487-3.372-16.627-9.373-22.627s-14.14-9.373-22.627-9.373h-416c-4.202 0-8.364-0.828-12.246-2.436s-7.41-3.965-10.381-6.937c-2.972-2.972-5.329-6.499-6.937-10.381s-2.436-8.044-2.436-12.246c0-4.202 0.828-8.364 2.436-12.246s3.965-7.41 6.937-10.381c2.972-2.972 6.499-5.329 10.381-6.937s8.044-2.436 12.246-2.436h416c8.487 0 16.627-3.372 22.627-9.373s9.373-14.14 9.373-22.627v-420.864c0-8.486 3.372-16.627 9.373-22.63 6.001-5.997 14.14-9.37 22.627-9.37s16.627 3.373 22.63 9.37c5.997 6.003 9.37 14.144 9.37 22.63v420.864c0 8.487 3.373 16.626 9.37 22.627 6.003 6.001 14.144 9.373 22.63 9.373h416c8.486 0 16.627 3.372 22.63 9.373 5.997 6.001 9.37 14.14 9.37 22.627s-3.373 16.627-9.37 22.627c-6.003 6.001-14.144 9.373-22.63 9.373v0z" />
<glyph unicode="&#xe919;" glyph-name="star" data-tags="bw-star" d="M251.392-96c-19.539 0.038-38.554 6.317-54.272 17.92-13.35 9.728-23.703 23.008-29.88 38.323-6.177 15.322-7.932 32.064-5.064 48.333l39.296 223.488c0.672 3.962 0.351 8.032-0.932 11.84s-3.493 7.238-6.428 9.984l-166.528 158.272c-12.113 11.374-20.749 25.951-24.907 42.038s-3.665 33.023 1.419 48.842c5.174 16.164 14.832 30.525 27.851 41.414s28.861 17.855 45.685 20.090l230.4 32.64c4.243 0.558 8.289 2.129 11.798 4.58s6.376 5.709 8.362 9.5l102.528 203.328c7.606 14.958 19.226 27.503 33.559 36.229s30.813 13.29 47.596 13.178v0c16.768 0.114 33.242-4.449 47.565-13.176s25.933-21.274 33.523-36.232v0l102.976-203.392c1.958-3.78 4.8-7.029 8.288-9.47 3.494-2.441 7.52-4.001 11.744-4.546l230.4-32.64c16.819-2.235 32.666-9.202 45.683-20.090s22.675-25.25 27.853-41.414c5.082-15.819 5.574-32.755 1.414-48.842-4.154-16.087-12.794-30.664-24.902-42.038l-166.4-158.272c-2.944-2.746-5.158-6.17-6.458-9.978-1.293-3.808-1.626-7.878-0.966-11.846l39.36-223.488c2.867-16.269 1.114-33.011-5.069-48.333-6.176-15.315-16.525-28.595-29.875-38.323-13.702-10.086-29.946-16.154-46.899-17.523-16.96-1.363-33.965 2.022-49.101 9.779l-206.336 105.536c-3.917 1.99-8.25 3.027-12.64 3.027-4.396 0-8.726-1.037-12.643-3.027l-205.888-105.6c-13.039-6.656-27.472-10.125-42.112-10.112v0zM511.874 800c-4.938 0.063-9.793-1.244-14.030-3.774s-7.689-6.187-9.972-10.562l-102.912-203.264c-6.607-12.996-16.265-24.197-28.147-32.646s-25.633-13.892-40.077-15.866l-230.080-32.64c-4.906-0.575-9.546-2.535-13.377-5.652s-6.694-7.261-8.255-11.948c-1.462-4.344-1.636-9.018-0.5-13.459s3.532-8.457 6.9-11.565l166.4-158.336c10.595-10.017 18.547-22.5 23.148-36.335 4.6-13.834 5.707-28.593 3.22-42.961l-38.976-223.552c-0.807-4.531-0.302-9.197 1.456-13.446 1.757-4.256 4.694-7.917 8.464-10.554 4.132-3.091 9.058-4.941 14.203-5.338s10.296 0.678 14.853 3.098l205.824 105.6c12.972 6.656 27.341 10.125 41.923 10.125 14.579 0 28.947-3.469 41.92-10.125l205.696-105.6c4.55-2.432 9.702-3.507 14.854-3.11 5.146 0.397 10.074 2.253 14.202 5.35 3.763 2.656 6.694 6.33 8.448 10.592s2.266 8.934 1.472 13.472l-39.296 223.424c-2.534 14.362-1.459 29.126 3.13 42.971 4.595 13.843 12.557 26.326 23.174 36.325l166.4 158.336c3.386 3.094 5.798 7.11 6.931 11.556 1.139 4.445 0.954 9.126-0.531 13.468-1.517 4.701-4.358 8.865-8.186 11.988s-8.474 5.072-13.382 5.612l-230.080 32.64c-14.4 2.024-28.090 7.498-39.917 15.956-11.821 8.458-21.427 19.649-27.987 32.62l-102.848 203.264c-2.298 4.38-5.76 8.038-10.010 10.569s-9.114 3.835-14.054 3.767v0z" />
<glyph unicode="&#xe91a;" glyph-name="list" data-tags="bw-list" d="M992.003 426.693h-773.694c-8.487 0-16.626 3.372-22.627 9.373s-9.373 14.14-9.373 22.627c0 8.487 3.372 16.627 9.373 22.628s14.14 9.372 22.627 9.372h773.694c8.486 0 16.627-3.372 22.63-9.372s9.37-14.141 9.37-22.628c0-8.487-3.366-16.626-9.37-22.627s-14.144-9.373-22.63-9.373zM32.001 426.95c-4.215-0.072-8.392 0.803-12.224 2.56-3.764 1.512-7.231 3.679-10.24 6.4-5.846 6.242-9.076 14.488-9.024 23.040-0.033 8.358 3.206 16.399 9.024 22.4 4.641 4.36 10.412 7.33 16.657 8.575s12.714 0.713 18.671-1.535c3.789-1.746 7.252-4.127 10.24-7.040 5.771-6.022 8.963-14.060 8.896-22.4 0.052-8.552-3.178-16.798-9.024-23.040-3.009-2.721-6.476-4.888-10.24-6.4-3.997-1.808-8.352-2.684-12.736-2.56zM991.991 576.006h-773.697c-8.487 0-16.627 3.372-22.627 9.373s-9.373 14.14-9.373 22.627c0 8.487 3.372 16.627 9.373 22.627s14.14 9.373 22.627 9.373h773.697c8.486 0 16.627-3.372 22.624-9.373 6.003-6.001 9.376-14.14 9.376-22.627s-3.373-16.627-9.376-22.627c-5.997-6.001-14.138-9.373-22.624-9.373zM32.765 576.006c-4.405-0.126-8.782 0.749-12.8 2.56-3.903 1.51-7.406 3.897-10.24 6.976-3.060 2.902-5.48 6.412-7.104 10.304-1.547 3.878-2.433 7.988-2.624 12.16 0.229 4.139 1.092 8.219 2.56 12.096 1.592 3.91 4.015 7.426 7.104 10.304 2.849 3.076 6.347 5.481 10.24 7.040 5.933 2.406 12.44 3.021 18.718 1.77s12.052-4.314 16.61-8.81c2.906-3.031 5.304-6.51 7.104-10.304 1.178-3.921 1.74-8.002 1.664-12.096 0.039-4.132-0.61-8.241-1.92-12.16-1.601-3.905-4.023-7.42-7.104-10.304-2.834-3.079-6.337-5.466-10.24-6.976-3.752-1.725-7.839-2.598-11.968-2.56zM991.991 277.382h-773.697c-8.487 0-16.627 3.372-22.627 9.373s-9.373 14.14-9.373 22.627c0 8.487 3.372 16.627 9.373 22.627s14.14 9.373 22.627 9.373h773.697c8.486 0 16.627-3.372 22.624-9.373 6.003-6.001 9.376-14.14 9.376-22.627s-3.373-16.626-9.376-22.627c-5.997-6.001-14.138-9.373-22.624-9.373zM32.765 277.255c-4.378 0.152-8.701 1.017-12.8 2.56-3.8 1.699-7.268 4.061-10.24 6.976-5.936 6.009-9.407 14.023-9.728 22.464 0.17 4.147 1.036 8.236 2.56 12.096 1.741 3.999 4.143 7.677 7.104 10.88 6.272 5.591 14.381 8.68 22.784 8.68s16.512-3.089 22.784-8.68c5.662-6.312 8.785-14.497 8.768-22.976 0.050-8.381-3.19-16.447-9.024-22.464-2.972-2.915-6.44-5.277-10.24-6.976-3.833-1.466-7.871-2.33-11.968-2.56v0zM992.003 128.006h-773.694c-8.487 0-16.626 3.373-22.627 9.37-6.001 6.003-9.373 14.144-9.373 22.63s3.372 16.627 9.373 22.63c6.001 5.997 14.14 9.37 22.627 9.37h773.694c8.486 0 16.627-3.373 22.63-9.37 6.003-6.003 9.37-14.144 9.37-22.63s-3.366-16.627-9.37-22.63c-6.003-5.997-14.144-9.37-22.63-9.37zM32.767 128.006c-4.406-0.128-8.782 0.749-12.8 2.56-3.903 1.51-7.406 3.898-10.24 6.976-5.857 6.010-9.121 14.074-9.088 22.464-0.072 8.346 3.174 16.384 9.024 22.336 6.126 5.882 14.291 9.165 22.784 9.165s16.657-3.283 22.784-9.165c5.756-5.997 8.908-14.022 8.768-22.336 0.050-8.378-3.19-16.448-9.024-22.464-2.835-3.078-6.338-5.466-10.24-6.976-3.752-1.722-7.839-2.598-11.968-2.56v0z" />
<glyph unicode="&#xe91b;" glyph-name="angle-down" data-tags="bw-angle-down" d="M1015.392 629.834c12.058-12.92 11.36-33.169-1.555-45.228l-414.5-386.865c-49.179-45.901-125.495-45.901-174.674 0l-414.497 386.865c-12.92 12.059-13.618 32.308-1.56 45.228s32.308 13.618 45.228 1.56l414.497-386.863c24.589-22.95 62.748-22.95 87.337 0l414.495 386.863c12.922 12.058 33.171 11.36 45.229-1.56z" />
<glyph unicode="&#xe91b;" glyph-name="angle-right" data-tags="bw-angle-right" d="M266.166 887.394c12.92 12.059 33.169 11.36 45.228-1.56l386.865-414.498c45.901-49.178 45.901-125.495 0-174.673l-386.865-414.5c-12.059-12.915-32.308-13.613-45.228-1.555s-13.618 32.307-1.56 45.229l386.863 414.495c22.95 24.589 22.95 62.748 0 87.337l-386.863 414.497c-12.058 12.92-11.36 33.169 1.56 45.228z" />
<glyph unicode="&#xe91c;" glyph-name="external-link" data-tags="bw-external-link" d="M992.002 364.351c-8.493 0-16.627-3.372-22.63-9.373s-9.37-14.14-9.37-22.627v-354.176c0.166-10.982-4.019-21.587-11.648-29.491s-18.074-12.467-29.056-12.685h-814.595c-10.983 0.218-21.432 4.781-29.060 12.685s-11.815 18.509-11.644 29.491v811.456c-0.11 5.455 0.855 10.878 2.841 15.96s4.954 9.722 8.734 13.657c3.78 3.934 8.298 7.085 13.296 9.274s10.378 3.37 15.833 3.478h348.544c8.487 0 16.627 3.371 22.628 9.373s9.372 14.14 9.372 22.627c0 8.487-3.372 16.626-9.372 22.627s-14.141 9.373-22.628 9.373h-348.544c-13.859-0.109-27.562-2.946-40.324-8.351s-24.336-13.27-34.058-23.147c-9.723-9.877-17.405-21.573-22.607-34.419s-7.824-26.591-7.715-40.451v-811.456c-0.17-27.955 10.759-54.842 30.39-74.746s46.358-31.213 74.314-31.43h814.595c27.955 0.218 54.682 11.526 74.31 31.43s30.56 46.79 30.394 74.746v354.176c0 8.487-3.373 16.627-9.376 22.627s-14.138 9.373-22.624 9.373zM958.844 895.999l-174.976-0.576c-8.448-0.373-16.422-3.992-22.272-10.102-5.843-6.11-9.107-14.241-9.107-22.698s3.264-16.587 9.107-22.698c5.85-6.11 13.824-9.729 22.272-10.102l97.856 0.384c0 0 12.8 0 8.128-10.496l-316.608-338.304c-5.536-6.422-8.378-14.739-7.917-23.207 0.454-8.468 4.166-16.433 10.362-22.223 6.202-5.791 14.394-8.959 22.874-8.842s16.589 3.512 22.618 9.472l319.168 341.12c0.787 0.602 1.709 1.002 2.682 1.164s1.978 0.081 2.912-0.235c0.934-0.316 1.779-0.859 2.458-1.579s1.165-1.596 1.421-2.55v0-107.456c-0.192-4.429 0.512-8.852 2.074-13.002 1.555-4.15 3.942-7.941 7.008-11.144s6.746-5.753 10.822-7.495c4.077-1.741 8.467-2.639 12.896-2.639 4.435 0 8.826 0.898 12.902 2.639s7.757 4.291 10.822 7.495c3.066 3.203 5.453 6.995 7.008 11.144 1.562 4.15 2.266 8.573 2.067 13.002l-0.576 172.8c-0.032 14.854-5.939 29.091-16.429 39.606s-24.717 16.454-39.571 16.522v0z" />
<glyph unicode="&#xe91d;" glyph-name="refresh" data-tags="bw-refresh" d="M1009.622 638.015c-6.669 3.825-14.566 4.895-22.016 2.983-7.443-1.913-13.85-6.657-17.856-13.223l-57.6-96.832c-1.005-1.662-2.451-3.009-4.186-3.887s-3.68-1.251-5.613-1.078c-1.933 0.173-3.782 0.886-5.331 2.058s-2.733 2.756-3.43 4.57c-36.41 101.348-108.269 186.097-202.304 238.592-105.747 60.056-230.767 76.43-348.413 45.632-117.227-29.444-217.977-104.207-280.128-207.872-30.484-50.468-50.396-106.604-58.526-165s-4.31-117.836 11.23-174.712c15.533-57.318 42.372-110.944 78.94-157.734s82.124-85.792 133.988-114.714c105.749-60.051 230.769-76.422 348.413-45.632 97.837 24.672 184.755 81.043 247.168 160.32 2.323 2.95 4.026 6.336 5.011 9.952 0.986 3.622 1.235 7.405 0.736 11.123s-1.741 7.296-3.654 10.528c-1.907 3.226-4.448 6.042-7.469 8.269-6.176 4.582-13.888 6.592-21.517 5.6s-14.573-4.902-19.379-10.912c-54.477-69.203-130.342-118.419-215.744-139.968-102.709-26.867-211.85-12.582-304.189 39.808-45.217 25.299-84.925 59.373-116.796 100.224s-55.263 87.654-68.804 137.664c-13.568 49.663-16.905 101.565-9.809 152.557s24.477 100.010 51.089 144.083c54.184 90.289 141.929 155.436 244.032 181.184 102.732 26.861 211.889 12.579 304.253-39.808 80.314-44.867 142.176-116.682 174.656-202.752 0.685-1.968 0.742-4.098 0.179-6.102-0.57-2.004-1.734-3.786-3.347-5.104s-3.59-2.111-5.67-2.271c-2.074-0.159-4.147 0.323-5.946 1.38l-99.776 56.256c-6.81 3.921-14.886 5.024-22.502 3.075s-14.17-6.796-18.266-13.507c-1.85-3.218-3.046-6.773-3.507-10.458-0.467-3.685-0.186-7.425 0.813-11.002 1.005-3.576 2.707-6.916 5.018-9.825s5.178-5.328 8.429-7.114l182.4-103.36c6.701-3.617 14.534-4.532 21.888-2.56 3.68 0.925 7.136 2.566 10.176 4.829 3.046 2.262 5.613 5.101 7.552 8.355l106.112 177.6c1.939 3.206 3.206 6.769 3.731 10.476 0.531 3.707 0.307 7.482-0.653 11.101s-2.643 7.007-4.941 9.964c-2.298 2.956-5.171 5.42-8.442 7.243z" />
<glyph unicode="&#xe91e;" glyph-name="discourse" data-tags="bw-discourse" d="M516.343 896c-280.229 0-516.343-225.143-516.343-503.086 0-8.914 0.229-520.914 0.229-520.914l516.114 0.457c280.457 0 507.657 233.829 507.657 511.771s-227.2 511.771-507.657 511.771zM512 91.429c-44.343 0-86.629 9.829-124.343 27.657l-185.371-45.943 52.343 171.429c-22.4 41.371-35.2 88.914-35.2 139.429 0 161.6 130.971 292.571 292.571 292.571s292.571-130.971 292.571-292.571c0-161.6-130.971-292.571-292.571-292.571z" />
@@ -71,7 +71,7 @@
<glyph unicode="&#xe92a;" glyph-name="sign-out" data-tags="bw-sign-out" d="M929.28 417.6l-119.616 128c-5.946 6.348-14.163 10.076-22.854 10.364-8.685 0.288-17.133-2.888-23.482-8.828s-10.080-14.16-10.368-22.85c-0.288-8.69 2.886-17.138 8.832-23.486l66.816-71.488c0 0 8.576-9.536-2.176-13.12l-463.427 1.664c-4.308 0.017-8.578-0.816-12.564-2.451s-7.611-4.041-10.666-7.078c-3.055-3.037-5.482-6.648-7.14-10.625s-2.516-8.241-2.525-12.55c-0.017-8.696 3.417-17.044 9.548-23.212s14.46-9.65 23.156-9.684l467.203-1.6c0.973-0.153 1.901-0.547 2.688-1.147s1.408-1.386 1.818-2.289c0.41-0.902 0.582-1.891 0.512-2.878-0.077-0.988-0.39-1.941-0.922-2.774v0l-78.464-73.536c-3.149-2.941-5.69-6.474-7.469-10.397-1.786-3.92-2.778-8.157-2.918-12.464-0.147-4.307 0.557-8.595 2.067-12.627 1.517-4.038 3.808-7.731 6.752-10.88 2.938-3.149 6.47-5.683 10.394-7.469 3.923-1.779 8.16-2.771 12.461-2.918 4.307-0.147 8.602 0.557 12.634 2.074 4.032 1.51 7.731 3.802 10.88 6.746l126.208 118.080c10.861 10.193 17.242 24.28 17.734 39.169s-4.947 29.366-15.11 40.255v0zM928-128h-832c-25.46 0-49.879 10.112-67.882 28.115s-28.118 42.426-28.118 67.885v832c0 25.46 10.115 49.879 28.118 67.882s42.422 28.118 67.882 28.118h827.136c25.459 0 49.882-10.114 67.885-28.118s28.115-42.422 28.115-67.882v-137.92c0-8.487-3.373-16.627-9.37-22.627-6.003-6.001-14.144-9.373-22.63-9.373s-16.627 3.372-22.63 9.373c-5.997 6.001-9.37 14.14-9.37 22.627v137.92c0 8.487-3.373 16.627-9.37 22.627-6.003 6.001-14.144 9.373-22.63 9.373h-827.136c-8.487 0-16.627-3.372-22.627-9.373s-9.373-14.14-9.373-22.627v-832c0-8.486 3.372-16.627 9.373-22.63 6.001-5.997 14.14-9.37 22.627-9.37h832c8.486 0 16.627 3.373 22.63 9.37 5.997 6.003 9.37 14.144 9.37 22.63v135.36c0 8.486 3.373 16.627 9.37 22.63 6.003 5.997 14.144 9.37 22.63 9.37s16.627-3.373 22.63-9.37c5.997-6.003 9.37-14.144 9.37-22.63v-135.36c0-25.459-10.112-49.882-28.115-67.885s-42.426-28.115-67.885-28.115z" />
<glyph unicode="&#xe92b;" glyph-name="share" data-tags="bw-share" d="M885.207 149.567c-23.891-0.077-47.36-6.33-68.122-18.163-20.755-11.834-38.099-28.838-50.342-49.357l-504.126 239.68c9.807 18.616 15.087 39.283 15.409 60.322s-4.321 41.858-13.553 60.766l504.254 240.064c18.035-27.951 45.542-48.461 77.478-57.769s66.157-6.788 96.384 7.096c30.227 13.884 54.432 38.202 68.179 68.492s16.115 64.522 6.662 96.413c-9.453 31.892-30.093 59.307-58.125 77.214s-61.581 25.107-94.49 20.277c-32.909-4.83-62.976-21.364-84.685-46.571-21.702-25.206-33.594-57.394-33.485-90.657 0.096-10.926 1.517-21.8 4.224-32.384l-512.766-244.032c-19.144 19.636-43.676 33.163-70.5 38.871s-54.738 3.344-80.219-6.796c-25.482-10.14-47.389-27.599-62.957-50.177s-24.1-49.26-24.52-76.682c-0.419-27.421 7.293-54.353 22.164-77.395s36.234-41.164 61.394-52.077c25.16-10.913 52.988-14.132 79.974-9.242 26.986 4.883 51.92 17.655 71.655 36.698l514.43-244.48c-5.978-28.070-3.11-57.306 8.211-83.68 11.315-26.374 30.534-48.589 55.002-63.59 24.474-15.002 52.986-22.054 81.626-20.179s55.994 12.582 78.298 30.65c22.304 18.061 38.464 42.592 46.246 70.221 7.782 27.622 6.816 56.986-2.778 84.032-9.587 27.053-27.328 50.47-50.771 67.021-23.45 16.557-51.45 25.434-80.154 25.414v0zM885.207 850.495c18.432 0 36.454-5.467 51.776-15.709 15.328-10.242 27.27-24.799 34.323-41.83s8.89-35.77 5.293-53.848c-3.603-18.078-12.48-34.682-25.523-47.711-13.037-13.029-29.645-21.899-47.725-25.487s-36.819-1.734-53.843 5.329c-17.030 7.063-31.578 19.016-41.811 34.349-10.227 15.332-15.686 33.355-15.674 51.788 0.038 24.719 9.882 48.414 27.379 65.876s41.216 27.261 65.933 27.244h-0.128zM139.097 290.815c-18.427 0-36.441 5.463-51.763 15.7s-27.267 24.786-34.321 41.809c-7.055 17.023-8.904 35.756-5.313 53.83s12.459 34.678 25.484 47.713c13.026 13.035 29.624 21.914 47.695 25.517s36.806 1.766 53.834-5.277c17.028-7.043 31.586-18.977 41.833-34.292s15.723-33.325 15.735-51.752c0-24.72-9.815-48.428-27.289-65.914s-41.175-27.317-65.895-27.334zM885.207-82.369c-18.432 0-36.448 5.466-51.776 15.706-15.322 10.246-27.27 24.8-34.317 41.83-7.053 17.030-8.896 35.776-5.293 53.85 3.597 18.080 12.48 34.682 25.517 47.712 13.043 13.030 29.651 21.901 47.731 25.485 18.080 3.59 36.819 1.734 53.843-5.325 17.024-7.066 31.578-19.021 41.805-34.349 10.234-15.334 15.686-33.357 15.674-51.789-0.051-24.691-9.882-48.358-27.354-65.811-17.466-17.453-41.133-27.277-65.83-27.309v0z" />
<glyph unicode="&#xe92c;" glyph-name="clock" data-tags="bw-clock" d="M512-127.999c-101.264 0-200.254 30.029-284.452 86.285-84.198 56.262-149.822 136.224-188.574 229.779-38.752 93.558-48.892 196.504-29.136 295.822s68.519 190.548 140.124 262.152c71.604 71.604 162.834 120.368 262.152 140.123s202.267 9.616 295.822-29.136c93.555-38.752 173.517-104.377 229.779-188.574 56.256-84.198 86.285-183.188 86.285-284.452-0.154-135.744-54.144-265.888-150.131-361.869-95.981-95.987-226.125-149.978-361.869-150.131zM512 832.001c-88.606 0-175.222-26.275-248.895-75.502s-131.094-119.195-165.002-201.056c-33.908-81.861-42.78-171.939-25.494-258.843s59.954-166.73 122.608-229.386c62.653-62.65 142.479-105.318 229.383-122.605s176.981-8.416 258.844 25.491c81.862 33.914 151.827 91.334 201.056 165.005s75.501 160.29 75.501 248.896c-0.134 118.775-47.379 232.647-131.366 316.634s-197.856 131.23-316.634 131.366v0zM387.197 225.793c-6.404 0-12.661 1.92-17.962 5.51s-9.403 8.691-11.774 14.643c-2.371 5.946-2.903 12.468-1.526 18.723s4.597 11.951 9.246 16.355l116.416 110.4v323.264c0 8.487 3.372 16.627 9.372 22.628s14.141 9.372 22.63 9.372c8.486 0 16.621-3.372 22.624-9.372s9.376-14.141 9.376-22.628v-337.088c-0.013-4.337-0.896-8.627-2.618-12.609-1.715-3.983-4.224-7.575-7.366-10.559l-126.403-119.807c-5.937-5.658-13.817-8.819-22.016-8.832v0z" />
<glyph unicode="&#xe92d;" glyph-name="angle-left" data-tags="bw-angle-left" d="M722.515-119.392c-12.915-12.058-33.165-11.36-45.229 1.555l-386.861 414.5c-45.9 49.179-45.9 125.495 0 174.674l386.861 414.497c12.064 12.92 32.314 13.618 45.229 1.56 12.922-12.059 13.619-32.308 1.562-45.228l-386.864-414.497c-22.95-24.589-22.95-62.748-0.001-87.337l386.865-414.495c12.058-12.922 11.36-33.171-1.562-45.229z" />
<glyph unicode="&#xe92d;" glyph-name="angle-down" data-tags="bw-angle-down" d="M1015.392 629.834c12.058-12.92 11.36-33.169-1.555-45.228l-414.5-386.865c-49.179-45.901-125.495-45.901-174.674 0l-414.497 386.865c-12.92 12.059-13.618 32.308-1.56 45.228s32.308 13.618 45.228 1.56l414.497-386.863c24.589-22.95 62.748-22.95 87.337 0l414.495 386.863c12.922 12.058 33.171 11.36 45.229-1.56z" />
<glyph unicode="&#xe92e;" glyph-name="caret-left" data-tags="bw-caret-left" d="M267.171 406.468c-19.731-19.357-16.259-27.798 3.196-46.654l392.431-410.214c26.573-26.611 40.192-19.264 40.192 19.213v831.921c0 36.2-13.734 46.248-40.019 19.926l-395.799-414.191zM212.801 327.454c-27.734 34.086-27.734 80.554 0 114.64l398.166 415.959c68.419 71.587 157.033 35.957 157.033-57.32v-831.921c0-93.274-95.936-132.41-157.033-57.318l-398.166 415.96z" />
<glyph unicode="&#xe92f;" glyph-name="square" data-tags="bw-square" horiz-adv-x="1280" d="M1056-127.616h-832c-25.46 0-49.879 10.112-67.882 28.115s-28.118 42.426-28.118 67.885v832c0 25.46 10.115 49.879 28.118 67.882s42.422 28.118 67.882 28.118h832c25.459 0 49.882-10.114 67.885-28.118s28.115-42.422 28.115-67.882v-832c0-25.459-10.112-49.882-28.115-67.885s-42.426-28.115-67.885-28.115zM224 832.384c-8.487 0-16.627-3.371-22.627-9.373s-9.373-14.141-9.373-22.627v-832c0-8.486 3.372-16.627 9.373-22.63 6.001-5.997 14.14-9.37 22.627-9.37h832c8.486 0 16.627 3.373 22.63 9.37 5.997 6.003 9.37 14.144 9.37 22.63v832c0 8.486-3.373 16.626-9.37 22.627-6.003 6.001-14.144 9.373-22.63 9.373h-832z" />
<glyph unicode="&#xe930;" glyph-name="collection" data-tags="bw-collection" d="M928-64h-831.999c-25.586 0.166-50.061 10.477-68.057 28.666s-28.045 42.771-27.943 68.358v491.072c-0.017 25.531 10.069 50.031 28.056 68.15s42.413 28.385 67.944 28.554h831.999c25.53-0.169 49.958-10.435 67.942-28.554 17.99-18.119 28.077-42.62 28.058-68.15v-491.072c0.102-25.587-9.946-50.17-27.942-68.358s-42.47-28.499-68.058-28.666v0zM96.001 556.416c-8.487 0-16.626-3.372-22.627-9.373s-9.373-14.14-9.373-22.627v-491.072c-0.042-4.23 0.755-8.422 2.344-12.346 1.589-3.917 3.939-7.482 6.915-10.49s6.518-5.389 10.421-7.021c3.903-1.626 8.091-2.464 12.32-2.464h831.999c4.23 0 8.416 0.838 12.32 2.464 3.904 1.632 7.45 4.013 10.419 7.021 2.976 3.008 5.331 6.573 6.918 10.49 1.587 3.923 2.387 8.115 2.342 12.346v490.752c0 8.487-3.373 16.626-9.37 22.627-6.003 6.001-14.144 9.373-22.63 9.373l-831.999 0.32zM906.88 682.752h-798.787c-5.792 0.308-11.245 2.826-15.236 7.035s-6.216 9.789-6.216 15.589c0 5.8 2.225 11.38 6.216 15.589s9.444 6.726 15.236 7.035h798.787c5.792-0.308 11.245-2.826 15.232-7.035 3.994-4.209 6.214-9.789 6.214-15.589s-2.221-11.38-6.214-15.589c-3.987-4.209-9.44-6.726-15.232-7.035v0zM858.24 786.752h-701.507c-5.792 0.308-11.245 2.826-15.236 7.035s-6.216 9.789-6.216 15.589c0 5.8 2.225 11.38 6.216 15.589s9.444 6.726 15.236 7.035h701.507c5.792-0.308 11.245-2.826 15.232-7.035 3.994-4.209 6.221-9.789 6.221-15.589s-2.227-11.38-6.221-15.589c-3.987-4.209-9.44-6.726-15.232-7.035v0z" />
@@ -131,9 +131,9 @@
<glyph unicode="&#xe966;" glyph-name="youtube" data-tags="bw-youtube" d="M836.909 760.15c-153.773 10.496-496.258 10.453-649.816 0-166.272-11.349-185.856-111.787-187.093-376.149 1.237-263.892 20.651-364.756 187.093-376.148 153.6-10.458 496.043-10.496 649.816 0 166.272 11.347 185.856 111.782 187.091 376.148-1.235 263.893-20.653 364.757-187.091 376.149v0zM384 213.331v341.336l341.331-170.368-341.331-170.968z" />
<glyph unicode="&#xe967;" glyph-name="ban" data-tags="bw-ban" d="M512 832c-246.976 0-448-201.024-448-448s201.024-448 448-448c246.976 0 448 201.024 448 448s-201.024 448-448 448zM512-128c-282.304 0-512 229.696-512 512s229.696 512 512 512c282.304 0 512-229.696 512-512s-229.696-512-512-512zM859.271 686.007l-649.28-649.28c-15.269-15.269-37.789-17.59-50.349-5.030-12.495 12.495-10.174 35.016 5.094 50.285l649.28 649.28c15.269 15.269 37.789 17.59 50.285 5.094 12.56-12.56 10.239-35.081-5.030-50.349z" />
<glyph unicode="&#xe968;" glyph-name="camera" data-tags="bw-camera" d="M924.352 704h-821.952c-27.136 0-53.184-11.264-72.384-31.36s-30.016-47.296-30.016-75.712v-553.856c0-28.416 10.816-55.616 30.016-75.712s45.248-31.36 72.384-31.36h819.2c27.136 0 53.184 11.264 72.384 31.36s30.016 47.296 30.016 75.712v553.856c0 27.904-10.432 54.72-28.992 74.752-18.624 19.968-43.968 31.552-70.656 32.32zM955.712 43.072c0-9.472-3.584-18.56-9.984-25.216-6.4-6.72-15.104-10.496-24.128-10.496h-819.2c-9.024 0-17.728 3.776-24.128 10.496-6.4 6.656-9.984 15.744-9.984 25.216v553.856c0 9.472 3.584 18.56 9.984 25.216 6.4 6.72 15.104 10.496 24.128 10.496h819.2c9.024 0 17.728-3.776 24.128-10.496 6.4-6.656 9.984-15.744 9.984-25.216v-553.856zM876.806 571.565h-139.14c-3.567 0-6.989-2.624-9.511-7.296-2.522-4.736-3.927-11.072-3.927-17.664 0-6.656 1.405-12.992 3.927-17.664 2.522-4.736 5.945-7.36 9.511-7.36h137.987c3.567 0 6.989 2.624 9.511 7.36 2.522 4.672 3.927 11.008 3.927 17.664 0 6.592-1.405 12.928-3.927 17.664-2.522 4.672-5.945 7.296-9.511 7.296h1.153zM185.6 652.8l76.8 102.4c6.016 8.064 15.552 12.8 25.6 12.8 0 0 448 0 448 0 10.048 0 19.584-4.736 25.6-12.8l76.8-102.4 51.2 38.4-76.8 102.4c-18.112 24.192-46.592 38.4-76.8 38.4h-448c-30.208 0-58.688-14.208-76.8-38.4 0 0-76.8-102.4-76.8-102.4l51.2-38.4zM512 544c-123.648 0-224-100.352-224-224s100.352-224 224-224c123.648 0 224 100.352 224 224s-100.352 224-224 224zM512 499.2c98.88 0 179.2-80.32 179.2-179.2s-80.32-179.2-179.2-179.2c-98.88 0-179.2 80.32-179.2 179.2s80.32 179.2 179.2 179.2z" />
<glyph unicode="&#xe969;" glyph-name="angle-right" data-tags="bw-angle-right" d="M266.166 887.394c12.92 12.059 33.169 11.36 45.228-1.56l386.865-414.498c45.901-49.178 45.901-125.495 0-174.673l-386.865-414.5c-12.059-12.915-32.308-13.613-45.228-1.555s-13.618 32.307-1.56 45.229l386.863 414.495c22.95 24.589 22.95 62.748 0 87.337l-386.863 414.497c-12.058 12.92-11.36 33.169 1.56 45.228z" />
<glyph unicode="&#xe969;" glyph-name="angle-up" data-tags="bw-angle-up" d="M8.606 173.485c-12.059 12.915-11.36 33.165 1.56 45.229l414.498 386.861c49.178 45.9 125.495 45.9 174.673 0l414.5-386.861c12.915-12.064 13.613-32.314 1.555-45.229-12.058-12.922-32.307-13.619-45.229-1.562l-414.495 386.864c-24.589 22.95-62.748 22.95-87.337 0.001l-414.497-386.865c-12.92-12.058-33.169-11.36-45.228 1.562z" />
<glyph unicode="&#xe96a;" glyph-name="desktop" data-tags="bwi-desktop" d="M270.651 0.733c0 14.498 11.753 26.252 26.252 26.252h438.020c14.498 0 26.252-11.754 26.252-26.252s-11.754-26.252-26.252-26.252h-438.020c-14.499 0-26.252 11.754-26.252 26.252zM411.444 0.733v125.15h52.502v-125.15h-52.502zM552.235 0.733v125.15h52.502v-125.15h-52.502zM-10.935 725.265c0 37.695 30.558 68.254 68.254 68.254h909.364c37.695 0 68.253-30.557 68.253-68.254v-557.383c0-37.695-30.557-68.254-68.253-68.254h-909.364c-37.695 0-68.254 30.557-68.254 68.254v557.383zM57.319 741.017c-8.698 0-15.75-7.052-15.75-15.75v-557.383c0-8.699 7.052-15.75 15.75-15.75h909.364c8.698 0 15.75 7.051 15.75 15.75v557.383c0 8.698-7.052 15.75-15.75 15.75h-909.364zM82.93 662.262c0 20.298 16.453 36.751 36.751 36.751h787.537c20.298 0 36.751-16.453 36.751-36.751v-430.52c0-20.298-16.453-36.751-36.751-36.751h-787.537c-20.298 0-36.751 16.453-36.751 36.751v430.52zM119.681 667.513c-2.899 0-5.251-2.35-5.251-5.251v-430.52c0-2.899 2.35-5.251 5.251-5.251h787.537c2.899 0 5.251 2.35 5.251 5.251v430.52c0 2.899-2.35 5.251-5.251 5.251h-787.537z" />
<glyph unicode="&#xe96b;" glyph-name="angle-up" data-tags="bw-angle-up" d="M8.606 173.485c-12.059 12.915-11.36 33.165 1.56 45.229l414.498 386.861c49.178 45.9 125.495 45.9 174.673 0l414.5-386.861c12.915-12.064 13.613-32.314 1.555-45.229-12.058-12.922-32.307-13.619-45.229-1.562l-414.495 386.864c-24.589 22.95-62.748 22.95-87.337 0.001l-414.497-386.865c-12.92-12.058-33.169-11.36-45.228 1.562z" />
<glyph unicode="&#xe96b;" glyph-name="angle-left" data-tags="bw-angle-left" d="M722.515-119.392c-12.915-12.058-33.165-11.36-45.229 1.555l-386.861 414.5c-45.9 49.179-45.9 125.495 0 174.674l386.861 414.497c12.064 12.92 32.314 13.618 45.229 1.56 12.922-12.059 13.619-32.308 1.562-45.228l-386.864-414.497c-22.95-24.589-22.95-62.748-0.001-87.337l386.865-414.495c12.058-12.922 11.36-33.171-1.562-45.229z" />
<glyph unicode="&#xe96c;" glyph-name="share-arrow" data-tags="bw-share-arrow" d="M926.469 752.44l-232.749 134.772c-7.044 4.125-15.434 5.29-23.337 3.242s-14.675-7.143-18.83-14.17c-2.043-3.454-3.383-7.281-3.939-11.258s-0.314-8.024 0.71-11.907c1.019-3.883 2.803-7.524 5.248-10.711 2.439-3.188 5.489-5.859 8.97-7.858l165.023-95.507c-115.078-23.892-751.894-183.976-750.165-804.989 0.114-8.113 3.416-15.854 9.193-21.546 5.776-5.698 13.564-8.89 21.675-8.89v0c8.134 0 15.939 3.21 21.719 8.934s9.069 13.495 9.15 21.626c-2.814 124.382 28.738 247.127 91.184 354.733s153.366 195.9 262.755 255.167c109.934 65.243 230.062 111.513 355.357 136.871l-99.705-168.48c-2.037-3.457-3.37-7.284-3.92-11.258s-0.309-8.020 0.716-11.9c1.019-3.881 2.803-7.52 5.235-10.708 2.439-3.188 5.482-5.862 8.958-7.868 4.025-2.316 8.52-3.707 13.15-4.075 5.767-0.476 11.551 0.678 16.694 3.331 5.137 2.652 9.433 6.696 12.384 11.671l135.821 229.044c2.025 3.476 3.34 7.319 3.871 11.306 0.525 3.988 0.253 8.040-0.797 11.923s-2.865 7.517-5.328 10.694c-2.469 3.177-5.544 5.832-9.044 7.812v0zM-44.466 1588.254c-0.005 0-0.010-0.006-0.015-0.006l-0.030-0.006-0.297-0.043-1.379-0.241c-1.262-0.216-3.202-0.561-5.773-1.050-5.143-0.975-12.811-2.506-22.643-4.705-19.67-4.402-47.951-11.446-81.963-21.966-68.134-21.065-158.667-55.883-248.88-110.841-179.916-109.608-356.857-297.814-355.968-618.218 0.049-17.048-13.736-30.907-30.782-30.954-17.052-0.047-30.905 13.736-30.955 30.784-0.963 348.516 193.567 554.131 385.585 671.113 95.755 58.334 191.304 95.006 262.762 117.102 35.784 11.063 65.676 18.521 86.729 23.231 10.531 2.352 18.863 4.019 24.628 5.112 2.883 0.55 5.126 0.951 6.682 1.222l1.814 0.309 0.666 0.105 0.052 0.013c0.018 0 0.035 0.006 4.894-30.48l-4.859 30.486c16.836 2.68 32.66-8.792 35.343-25.627 2.683-16.829-8.781-32.646-25.606-35.339-0.001 0-0.001 0-0.003 0z" />
<glyph unicode="&#xe96d;" glyph-name="eye-slash" data-tags="bw-eye-slash" d="M919.609 358.255c-76.096-69.696-229.952-189.504-406.976-189.504-51.2 0-97.792 9.024-144.576 24.896l63.808 63.872c24.576-14.144 49.984-22.272 80.192-22.080 84.096 0.704 151.232 63.936 151.808 147.776 0.192 30.144-8.512 58.944-23.936 82.304l82.752 82.752c84.544-41.088 155.136-97.92 198.4-138.432 15.36-14.336 14.144-37.312-1.472-51.584zM407.417 383.279c-1.28 58.432 46.784 104 104.256 104 17.28 0 33.6-4.224 48.064-11.584l-139.712-139.712c-7.808 14.016-12.608 30.080-12.608 47.296zM615.545 383.279c-1.216-55.616-46.592-99.584-103.168-100.416-16.768-0.192-29.568 2.88-45.376 9.792l137.792 137.792c7.232-13.888 10.752-30.464 10.752-47.168zM103.609 357.999c-16.384 14.72-16.96 38.336-1.6 52.672 76.16 71.36 231.168 189.568 410.048 190.592 52.032 0.256 100.224-9.536 146.816-26.432l-64.384-64.384c-24.384 15.232-51.84 24.32-82.56 24.32-84.096 0-154.24-67.2-153.024-151.488 0.448-30.528 10.048-58.56 26.24-82.176l-81.28-81.28c-85.824 40.512-155.904 98.304-200.256 138.176zM1007.545 425.583c-6.016 7.040-93.248 108.224-229.312 178.304l174.144 173.952c12.544 12.544 13.504 32 0.96 44.48-12.48 12.672-33.472 13.376-46.016 0.896l-190.976-190.912c-62.592 25.088-130.496 40.96-205.44 40.96-278.848 0.192-482.688-236.48-498.048-253.952-17.472-19.776-17.088-49.024 0.32-66.304 6.080-7.104 113.728-124.288 236.928-187.008l-176.384-176.384c-12.48-12.48-13.248-31.872-0.768-44.416 6.272-6.272 14.464-9.408 22.656-9.408s17.088 2.432 23.296 8.576l192.96 193.088c62.080-24.448 126.464-40.64 200.384-40.192 280.064 1.728 478.72 228.608 494.976 246.784 1.408 1.6 3.584 3.648 4.864 5.312 17.664 22.464 15.488 56.32-4.544 76.224z" />
<glyph unicode="&#xe96e;" glyph-name="file" data-tags="bw-file" d="M896-32c0-17.664-14.336-32-32-32h-704c-17.664 0-32 14.336-32 32v832c0 17.664 14.336 32 32 32h397.632c17.664 0 32-14.336 32-32v-194.496c0-53.056 43.008-96 96-96h178.368c17.664 0 32-14.4 32-32v-509.504zM653.632 764.8c0 11.072 13.504 16.512 21.184 8.576l173.632-179.008c7.616-7.808 2.048-20.864-8.832-20.864h-153.984c-17.664 0-32 14.336-32 32v159.296zM941.952 589.952l-278.080 286.592c-12.032 12.48-28.608 19.456-45.952 19.456h-489.92c-35.392 0-64-28.608-64-64v-896c0-35.392 28.608-64 64-64h768c35.392 0 64 28.608 64 64v609.344c0 16.64-6.528 32.64-18.048 44.608z" />
@@ -185,6 +185,8 @@
<glyph unicode="&#xe99c;" glyph-name="up-down-btn" data-tags="bw-up-down-btn" d="M456.202-102.797l-348.145 248.678c-75.997 54.285-37.594 174.118 55.799 174.118h696.292c93.389 0 131.795-119.833 55.795-174.118l-348.143-248.678c-33.379-23.84-78.219-23.84-111.597 0zM493.4-50.714c11.126-7.949 26.073-7.949 37.199 0l348.146 248.672c25.331 18.093 12.531 58.042-18.598 58.042h-696.292c-31.131 0-43.932-39.949-18.6-58.042l348.145-248.672zM567.798 870.794l348.144-248.675c76-54.284 37.594-174.118-55.795-174.118h-696.292c-93.393 0-131.795 119.836-55.799 174.118l348.145 248.675c33.379 23.842 78.219 23.842 111.597 0zM530.6 818.716c-11.126 7.947-26.073 7.947-37.199 0l-348.145-248.675c-25.332-18.095-12.531-58.040 18.6-58.040h696.292c31.13 0 43.93 39.945 18.598 58.040l-348.146 248.675z" />
<glyph unicode="&#xe99d;" glyph-name="caret-up" data-tags="bw-caret-up" d="M534.468 628.829c-19.357 19.731-27.798 16.259-46.654-3.196l-410.214-392.431c-26.614-26.573-19.264-40.192 19.215-40.192h831.921c36.198 0 46.246 13.734 19.923 40.019l-414.191 395.799zM455.454 683.199c34.086 27.734 80.554 27.734 114.64 0l415.96-398.166c71.584-68.419 35.955-157.033-57.318-157.033h-831.921c-93.277 0-132.413 95.936-57.32 157.033l415.959 398.166z" />
<glyph unicode="&#xe99e;" glyph-name="caret-down" data-tags="bw-caret-down" d="M489.532 139.168c19.357-19.731 27.798-16.256 46.654 3.2l410.214 392.428c26.611 26.574 19.264 40.194-19.213 40.194l-831.921 0.001c-36.2 0-46.248-13.736-19.926-40.019l414.191-395.804zM568.546 84.8c-34.086-27.731-80.554-27.731-114.64 0l-415.959 398.167c-71.587 68.42-35.957 157.033 57.32 157.033h831.921c93.274 0 132.41-95.934 57.318-157.033l-415.96-398.167z" />
<glyph unicode="&#xe99f;" glyph-name="passkey" data-tags="passkey" d="M171.866 100.531c-28.794 0-52.141 23.341-52.141 52.134s23.347 52.134 52.141 52.134c28.794 0 52.134-23.341 52.134-52.134s-23.341-52.134-52.134-52.134zM336.23 423.597c-71.622 45.381-119.174 125.34-119.174 216.403 0 141.385 114.618 256 256.003 256s256-114.615 256-256c0-91.064-47.547-171.022-119.17-216.403 61.672-22.859 115.894-60.723 158.11-109.085 11.29-12.579 21.253-23.994 28.108-33.718l114.25 0.104 113.644-109.313-160-142.784-64 64-64-64-64 64-64-61.44-191.164 0.262c-42.392-59.162-113.22-97.222-192.632-95.571-126.49 2.63-226.842 105.030-224.154 228.717 2.694 123.683 107.418 221.813 233.907 219.18 7.2-0.15 14.323-0.623 21.344-1.406 6.304 4.097 13.485 8.49 21.619 13.196 18.816 10.858 38.643 20.2 59.309 27.859zM422.251 280.453l289.060 0.263c-8.424 9.26-19.715 20.508-36.449 33.795-55.383 43.498-125.517 69.489-201.804 69.489-47.366 0-92.361-10.020-132.924-28.029 33.408-18.139 61.646-44.193 82.116-75.517zM281.056 640c0-106.038 85.965-192 192.003-192s192 85.962 192 192-85.961 192-192 192c-106.038 0-192.003-85.962-192.003-192zM383.994 95.667l198.295-0.269 90.625 86.995 63.086-63.085 64 64 66.5-66.496 63.35 56.531-45.255 43.533-496.955-0.454-18.963 29.018c-28.486 43.592-78.259 73.319-136.051 74.522-92.531 1.926-166.694-69.562-168.589-156.589-1.894-86.976 69.011-161.408 161.498-163.334 57.805-1.203 108.877 26.438 139.277 68.858l19.181 26.771z" />
<glyph unicode="&#xe9a0;" glyph-name="lock-encrypted" data-tags="bw-lock-encrypted" d="M496 368c0-14.216-6.18-26.989-16-35.778v-44.222c0-17.673-14.327-32-32-32s-32 14.327-32 32v44.222c-9.82 8.789-16 21.562-16 35.778 0 26.509 21.491 48 48 48s48-21.491 48-48zM352 0c17.673 0 32 14.33 32 32s-14.327 32-32 32c-17.673 0-32-14.33-32-32s14.327-32 32-32zM480 0c17.673 0 32 14.33 32 32s-14.327 32-32 32c-17.673 0-32-14.33-32-32s14.327-32 32-32zM640 32c0-17.67-14.327-32-32-32s-32 14.33-32 32c0 17.67 14.327 32 32 32s32-14.33 32-32zM736 0c17.67 0 32 14.33 32 32s-14.33 32-32 32c-17.67 0-32-14.33-32-32s14.33-32 32-32zM896 32c0-17.67-14.33-32-32-32s-32 14.33-32 32c0 17.67 14.33 32 32 32s32-14.33 32-32zM192 640c0 139.201 115.547 256 256 256 140.401 0 256-116.204 256-256v-99.201c73.030-14.824 128-63.393 128-140.799v-208h32c88.365 0 160-71.635 160-160s-71.635-160-160-160h-512c-36.026 0-69.272 11.904-96.015 32h-31.985c-88.365 0-160 55.635-160 144v352c0 77.407 54.968 125.975 128 140.799v99.201zM352 192h416v208c0 53.020-42.982 80-96 80h-448c-53.020 0-96-26.98-96-80v-352c0-46.323 32.81-68.986 76.46-78.010-8.025 19.072-12.46 40.019-12.46 62.010 0 88.365 71.635 160 160 160zM448 832c-106.336 0-192-89.362-192-192v-96h384v96c0 103.132-85.612 192-192 192zM352 128c-53.020 0-96-42.982-96-96s42.98-96 96-96h512c53.018 0 96 42.982 96 96s-42.982 96-96 96h-512z" />
<glyph unicode="&#xe9ee;" glyph-name="rocket" data-tags="rocket" d="M650.515 648.267c33.538 33.532 87.904 33.532 121.443 0 33.538-33.538 33.538-87.904 0-121.443s-87.904-33.538-121.443 0c-33.532 33.538-33.532 87.904 0 121.443zM750.801 627.113c-21.855 21.856-57.284 21.856-79.134 0-21.856-21.855-21.856-57.284 0-79.134 21.855-21.855 57.284-21.855 79.134 0s21.855 57.284 0 79.134zM493.141 680.645c113.184 90.608 223.416 148.836 310.181 180.552 43.273 15.818 81.527 25.336 111.933 28.691 15.138 1.668 29.339 1.929 41.709 0.19 11.507-1.615 25.903-5.552 36.583-16.232l3.981-3.981c10.68-10.679 14.617-25.076 16.232-36.582 1.739-12.377 1.478-26.572-0.19-41.71-3.356-30.406-12.874-68.659-28.691-111.932-31.716-86.771-89.944-196.998-180.552-310.187-29.487-36.837-59.902-73.855-91.847-111.076l30.789-145.412c4.388-20.715-0.21-42.314-12.654-59.447l-89.489-123.202c-35.299-48.598-110.586-37.857-130.894 18.67l-31.576 87.886-74.269-74.269c-5.844-5.844-15.313-5.844-21.151 0-5.844 5.845-5.844 15.313 0 21.152l80.747 80.747-47.795 47.794-162.229-162.229c-5.844-5.844-15.313-5.844-21.151 0s-5.844 15.313 0 21.151l162.229 162.229-54.059 54.060-306.392-306.392c-5.844-5.844-15.313-5.844-21.151 0-5.844 5.845-5.844 15.313 0 21.151l306.391 306.392-54.059 54.060-61.939-61.939c-5.845-5.844-15.313-5.844-21.151 0-5.845 5.844-5.845 15.313 0 21.151l61.938 61.939-47.794 47.794-112.086-112.086c-5.844-5.844-15.312-5.844-21.151 0-5.844 5.845-5.844 15.313 0 21.152l109.675 109.669-100.568 36.13c-56.526 20.307-67.261 95.6-18.663 130.894l123.202 89.489c17.132 12.444 38.73 17.041 59.446 12.654l145.412-30.789c37.221 31.945 74.238 62.36 111.076 91.847zM952.797 829.985c-0.744 0.223-2.062 0.551-4.14 0.84-5.79 0.812-14.652 0.934-26.837-0.411-24.234-2.675-57.643-10.683-97.952-25.414-80.389-29.379-184.973-84.318-293.329-171.062-38.013-30.432-76.092-61.749-114.295-94.665-2.919-4.249-6.879-7.644-11.433-9.895-54.88-47.714-110.027-98.85-165.607-155.478l258.391-258.39c56.628 55.58 107.765 110.728 155.48 165.609 2.25 4.551 5.643 8.51 9.89 11.427 32.912 38.205 64.235 76.284 94.667 114.298 86.744 108.362 141.683 212.941 171.063 293.329 14.73 40.309 22.744 73.723 25.414 97.952 1.345 12.179 1.222 21.047 0.411 26.837-0.29 2.078-0.619 3.397-0.84 4.14l-0.875 0.875zM224.258 561.056c-4.141 0.875-8.464-0.045-11.889-2.533l-123.202-89.489c-9.719-7.058-7.573-22.117 3.736-26.177l96.234-34.573c45.935 47.161 91.519 90.548 136.803 131.242l-101.677 21.529zM663.214 202.25c-40.694-45.278-84.081-90.868-131.243-136.803l34.574-96.235c4.061-11.304 19.119-13.455 26.177-3.736l89.489 123.202c2.488 3.425 3.408 7.748 2.533 11.889l-21.529 101.678z" />
<glyph unicode="&#xe9ef;" glyph-name="ellipsis-h" data-tags="ellipsis-h" d="M876.633 299.353c-46.75 0-84.647 37.897-84.647 84.647s37.897 84.647 84.647 84.647c46.75 0 84.647-37.897 84.647-84.647s-37.897-84.647-84.647-84.647zM512 299.353c-46.75 0-84.647 37.897-84.647 84.647s37.897 84.647 84.647 84.647c46.75 0 84.647-37.897 84.647-84.647s-37.897-84.647-84.647-84.647zM147.367 299.353c-46.75 0-84.647 37.897-84.647 84.647s37.897 84.647 84.647 84.647c46.75 0 84.647-37.897 84.647-84.647s-37.897-84.647-84.647-84.647z" />
<glyph unicode="&#xe9f0;" glyph-name="ellipsis-v" data-tags="ellipsis-v" d="M427.353 19.367c0 46.75 37.897 84.647 84.647 84.647s84.647-37.897 84.647-84.647c0-46.75-37.897-84.647-84.647-84.647s-84.647 37.897-84.647 84.647zM427.353 384c0 46.75 37.897 84.647 84.647 84.647s84.647-37.897 84.647-84.647c0-46.75-37.897-84.647-84.647-84.647s-84.647 37.897-84.647 84.647zM427.353 748.633c0 46.75 37.897 84.647 84.647 84.647s84.647-37.897 84.647-84.647c0-46.75-37.897-84.647-84.647-84.647s-84.647 37.897-84.647 84.647z" />

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 289 KiB

View File

@@ -130,7 +130,7 @@ $icons: (
"hamburger": "\e972",
"bw-folder-open-f1": "\e93e",
"desktop": "\e96a",
"angle-up": "\e96b",
"angle-up": "\e969",
"user": "\e900",
"user-f": "\e901",
"key": "\e902",
@@ -158,7 +158,7 @@ $icons: (
"plus": "\e918",
"star": "\e919",
"list": "\e91a",
"angle-down": "\e91b",
"angle-down": "\e92d",
"external-link": "\e91c",
"refresh": "\e91d",
"search": "\e91f",
@@ -175,7 +175,7 @@ $icons: (
"sign-out": "\e92a",
"share": "\e92b",
"clock": "\e92c",
"angle-left": "\e92d",
"angle-left": "\e96b",
"caret-left": "\e92e",
"square": "\e92f",
"collection": "\e930",
@@ -225,7 +225,7 @@ $icons: (
"wrench": "\e965",
"ban": "\e967",
"camera": "\e968",
"angle-right": "\e969",
"angle-right": "\e91b",
"eye-slash": "\e96d",
"file": "\e96e",
"paste": "\e96f",
@@ -262,6 +262,8 @@ $icons: (
"up-down-btn": "\e99c",
"caret-up": "\e99d",
"caret-down": "\e99e",
"passkey": "\e99f",
"lock-encrypted": "\e9a0",
);
@each $name, $glyph in $icons {

View File

@@ -320,9 +320,13 @@ export class AddEditComponent implements OnInit, OnDestroy {
: this.collections.filter((c) => (c as any).checked).map((c) => c.id);
}
// Clear current Cipher Id to trigger "Add" cipher flow
// Clear current Cipher Id and Fido2Key if exists to trigger "Add" cipher flow
if (this.cloneMode) {
this.cipher.id = null;
if (this.cipher.type === CipherType.Login && this.cipher.login.fido2Key) {
this.cipher.login.fido2Key = null;
}
}
const cipher = await this.encryptCipher();

View File

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

View File

@@ -30,10 +30,10 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { Launchable } from "@bitwarden/common/vault/interfaces/launchable";
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@@ -63,6 +63,7 @@ export class ViewComponent implements OnDestroy, OnInit {
fieldType = FieldType;
checkPasswordPromise: Promise<number>;
folder: FolderView;
cipherType = CipherType;
private totpInterval: any;
private previousCipherId: string;
@@ -155,6 +156,18 @@ export class ViewComponent implements OnDestroy, OnInit {
}
async clone() {
if (this.cipher.login?.fido2Key) {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "passkeyNotCopied" },
content: { key: "passkeyNotCopiedAlert" },
type: SimpleDialogType.INFO,
});
if (!confirmed) {
return false;
}
}
if (await this.promptPassword()) {
this.onCloneCipher.emit(this.cipher);
return true;
@@ -304,7 +317,7 @@ export class ViewComponent implements OnDestroy, OnInit {
}
}
launch(uri: LoginUriView, cipherId?: string) {
launch(uri: Launchable, cipherId?: string) {
if (!uri.canLaunch) {
return;
}

View File

@@ -216,6 +216,19 @@ 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,7 +45,13 @@ export class VaultFilter {
cipherPassesFilter = cipher.isDeleted;
}
if (this.cipherType != null && cipherPassesFilter) {
cipherPassesFilter = cipher.type === this.cipherType;
//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;
}
}
if (this.selectedFolder && this.selectedFolderId == null && cipherPassesFilter) {
cipherPassesFilter = cipher.folderId == null;

View File

@@ -0,0 +1,4 @@
export interface Launchable {
launchUri: string;
canLaunch: boolean;
}

View File

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