diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index b0593ac2b20..97a8d3cc4ee 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -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?"
}
}
diff --git a/apps/browser/src/vault/popup/components/action-buttons.component.html b/apps/browser/src/vault/popup/components/action-buttons.component.html
index f63c1f1ac32..97e41f2f5a0 100644
--- a/apps/browser/src/vault/popup/components/action-buttons.component.html
+++ b/apps/browser/src/vault/popup/components/action-buttons.component.html
@@ -100,3 +100,41 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.html b/apps/browser/src/vault/popup/components/vault/add-edit.component.html
index 1373669ca17..4033437e59f 100644
--- a/apps/browser/src/vault/popup/components/vault/add-edit.component.html
+++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.html
@@ -21,6 +21,9 @@
+
{{ "type" | i18n }}
@@ -129,6 +132,17 @@
+
+
+
+
+
+ {{ "typePasskey" | i18n }}
+ {{ "passkeyTwoStepLogin" | i18n }}
+
+
+
+
{{ "authenticatorKeyTotp" | i18n }}
+
+
+
+ {{ "username" | i18n }}
+
+
+
+ {{ "typePasskey" | i18n }}
+ {{ "dateCreated" | i18n }} {{ cipher.creationDate | date : "short" }}
+
+
+
+
+
+ {{ "application" | i18n }}
+
+
+
+
+
diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.html b/apps/browser/src/vault/popup/components/vault/vault-filter.component.html
index 2008001aabc..2065181ba25 100644
--- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.html
+++ b/apps/browser/src/vault/popup/components/vault/vault-filter.component.html
@@ -70,7 +70,9 @@
{{ "typeLogin" | i18n }}
- {{ typeCounts.get(cipherType.Login) || 0 }}
+ {{
+ getTypeCountsSum(typeCounts, cipherType.Login, cipherType.Fido2Key)
+ }}
{{ typeCounts.get(cipherType.SecureNote) || 0 }}
-
-
-
-
{{ "typePasskey" | i18n }}
-
- {{ typeCounts.get(cipherType.Fido2Key) || 0 }}
-
-
diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts
index 4be0fe89fe1..519e7715ef3 100644
--- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts
+++ b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts
@@ -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, ...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))
diff --git a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts b/apps/browser/src/vault/popup/components/vault/vault-items.component.ts
index 59d7ae92c28..62f0aa14c69 100644
--- a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts
+++ b/apps/browser/src/vault/popup/components/vault/vault-items.component.ts
@@ -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;
diff --git a/apps/browser/src/vault/popup/components/vault/view.component.html b/apps/browser/src/vault/popup/components/vault/view.component.html
index 99adef3b7c6..f290becfe48 100644
--- a/apps/browser/src/vault/popup/components/vault/view.component.html
+++ b/apps/browser/src/vault/popup/components/vault/view.component.html
@@ -201,6 +201,16 @@
+
+
+
+
+
+ {{ "typePasskey" | i18n }}
+ {{ "passkeyTwoStepLogin" | i18n }}
+
+
+
@@ -416,25 +426,54 @@
-
- {{ "keyType" | i18n }}
- {{ cipher.fido2Key.keyType }}
-
-
-
- {{ "keyCurve" | i18n }}
- {{ cipher.fido2Key.keyCurve }}
-
-
-
- {{ "rpName" | i18n }}
- {{ cipher.fido2Key.rpName }}
-
-
{{ "username" | i18n }}
{{ cipher.fido2Key.userName }}
+
+ {{ "typePasskey" | i18n }}
+ {{ "dateCreated" | i18n }} {{ cipher.creationDate | date : "short" }}
+
+
+
+
+
+
+ {{ "application" | i18n }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/browser/src/vault/popup/components/vault/view.component.ts b/apps/browser/src/vault/popup/components/vault/view.component.ts
index efdb48b95cd..d769f48f820 100644
--- a/apps/browser/src/vault/popup/components/vault/view.component.ts
+++ b/apps/browser/src/vault/popup/components/vault/view.component.ts
@@ -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,
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 9debdbc13fe..b8cf1f7fbc6 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -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?"
}
}
diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.html b/apps/desktop/src/vault/app/vault/add-edit.component.html
index c26078394ad..4b6d1ed5794 100644
--- a/apps/desktop/src/vault/app/vault/add-edit.component.html
+++ b/apps/desktop/src/vault/app/vault/add-edit.component.html
@@ -114,6 +114,16 @@
+
+
+ {{ "typePasskey" | i18n }}
+ {{ "passkeyTwoStepLogin" | i18n }}
+
+
{{ "authenticatorKeyTotp" | i18n }}
+
+
+
+ {{ "username" | i18n }}
+
+
+
+ {{ "typePasskey" | i18n }}
+ {{ "dateCreated" | i18n }} {{ cipher.creationDate | date : "short" }}
+
+
@@ -528,6 +556,24 @@
+
+
+
+
+ {{ "application" | i18n }}
+
+
+
+
+
diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts
index 5405a913a75..f6c3780412b 100644
--- a/apps/desktop/src/vault/app/vault/vault.component.ts
+++ b/apps/desktop/src/vault/app/vault/vault.component.ts
@@ -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;
}
diff --git a/apps/desktop/src/vault/app/vault/view.component.html b/apps/desktop/src/vault/app/vault/view.component.html
index ac23264c8d6..11327e14ca1 100644
--- a/apps/desktop/src/vault/app/vault/view.component.html
+++ b/apps/desktop/src/vault/app/vault/view.component.html
@@ -118,6 +118,11 @@
+
+
+ {{ "typePasskey" | i18n }}
+ {{ "passkeyTwoStepLogin" | i18n }}
+
{{ cipher.identity.country }}
+
+
+
+
+ {{ "username" | i18n }}
+ {{ cipher.fido2Key.userName }}
+
+
+
+ {{ "typePasskey" | i18n }}
+ {{ "dateCreated" | i18n }} {{ cipher.creationDate | date : "short" }}
+
+
@@ -465,6 +483,43 @@
{{ cipher.notes }}
+
+
+
+
+ {{ "application" | i18n }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html
index 664b3bbc380..15440024153 100644
--- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html
+++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html
@@ -103,6 +103,20 @@
{{ "launch" | i18n }}
+
+
+
+
+ {{ "launch" | i18n }}
+
+
+
{{ "attachments" | i18n }}
diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.html b/apps/web/src/app/vault/components/vault-items/vault-items.component.html
index 7179a0dfc83..12872ab3b00 100644
--- a/apps/web/src/app/vault/components/vault-items/vault-items.component.html
+++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.html
@@ -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)"
diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts
index 8c9ad1ac065..5449d442be3 100644
--- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts
+++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts
@@ -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 }));
diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.html b/apps/web/src/app/vault/individual-vault/add-edit.component.html
index a23332a2f96..3e03423c8b0 100644
--- a/apps/web/src/app/vault/individual-vault/add-edit.component.html
+++ b/apps/web/src/app/vault/individual-vault/add-edit.component.html
@@ -191,6 +191,25 @@
+
+
+
{{ "authenticatorKeyTotp" | i18n }}
@@ -813,6 +832,89 @@
+
+
+
+
+
+
{{ "notes" | i18n }}