mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 02:03:39 +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:
@@ -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 }}
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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 }));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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> = {}) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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?"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user