From afc46cc50a432961a56e051df6afd0510ee3735d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:40:50 -0600 Subject: [PATCH 1/3] [deps] Vault: Update @koa/router to v15 (#18086) * [deps] Vault: Update @koa/router to v15 * update router imports from `@koa/router` * remove `@types/koa__router` no longer needed with update to `@koa/router` --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Co-authored-by: Nick Krantz --- .github/renovate.json5 | 1 - apps/cli/package.json | 2 +- apps/cli/src/commands/serve.command.ts | 4 +- apps/cli/src/oss-serve-configurator.ts | 4 +- .../bit-cli/src/bit-serve-configurator.ts | 6 +- package-lock.json | 72 ++++++++++++------- package.json | 3 +- 7 files changed, 54 insertions(+), 38 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 718586a9b1a..6845e5f3829 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -313,7 +313,6 @@ "@types/inquirer", "@types/koa", "@types/koa__multer", - "@types/koa__router", "@types/koa-bodyparser", "@types/koa-json", "@types/lunr", diff --git a/apps/cli/package.json b/apps/cli/package.json index 93658cce361..79653ec970f 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -64,7 +64,7 @@ }, "dependencies": { "@koa/multer": "4.0.0", - "@koa/router": "14.0.0", + "@koa/router": "15.2.0", "big-integer": "1.6.52", "browser-hrtime": "1.1.8", "chalk": "4.1.2", diff --git a/apps/cli/src/commands/serve.command.ts b/apps/cli/src/commands/serve.command.ts index 5bf19333f35..92632981154 100644 --- a/apps/cli/src/commands/serve.command.ts +++ b/apps/cli/src/commands/serve.command.ts @@ -1,7 +1,7 @@ import http from "node:http"; import net from "node:net"; -import * as koaRouter from "@koa/router"; +import { Router } from "@koa/router"; import { OptionValues } from "commander"; import * as koa from "koa"; import * as koaBodyParser from "koa-bodyparser"; @@ -29,7 +29,7 @@ export class ServeCommand { ); const server = new koa(); - const router = new koaRouter(); + const router = new Router(); process.env.BW_SERVE = "true"; process.env.BW_NOINTERACTION = "true"; diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index e0385534cb7..fbf3c778725 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import * as koaMulter from "@koa/multer"; -import * as koaRouter from "@koa/router"; +import { Router } from "@koa/router"; import * as koa from "koa"; import { firstValueFrom, map } from "rxjs"; @@ -218,7 +218,7 @@ export class OssServeConfigurator { ); } - async configureRouter(router: koaRouter) { + async configureRouter(router: Router) { router.get("/generate", async (ctx, next) => { const response = await this.generateCommand.run(ctx.request.query); this.processResponse(ctx.response, response); diff --git a/bitwarden_license/bit-cli/src/bit-serve-configurator.ts b/bitwarden_license/bit-cli/src/bit-serve-configurator.ts index 71df651d9d0..5a00dccb59b 100644 --- a/bitwarden_license/bit-cli/src/bit-serve-configurator.ts +++ b/bitwarden_license/bit-cli/src/bit-serve-configurator.ts @@ -1,4 +1,4 @@ -import * as koaRouter from "@koa/router"; +import { Router } from "@koa/router"; import { OssServeConfigurator } from "@bitwarden/cli/oss-serve-configurator"; @@ -16,7 +16,7 @@ export class BitServeConfigurator extends OssServeConfigurator { super(serviceContainer); } - override async configureRouter(router: koaRouter): Promise { + override async configureRouter(router: Router): Promise { // Register OSS endpoints await super.configureRouter(router); @@ -24,7 +24,7 @@ export class BitServeConfigurator extends OssServeConfigurator { this.serveDeviceApprovals(router); } - private serveDeviceApprovals(router: koaRouter) { + private serveDeviceApprovals(router: Router) { router.get("/device-approval/:organizationId", async (ctx, next) => { if (await this.errorIfLocked(ctx.response)) { await next(); diff --git a/package-lock.json b/package-lock.json index 6352675d718..752f186e970 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", - "@koa/router": "14.0.0", + "@koa/router": "15.2.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "20.7.0", @@ -104,7 +104,6 @@ "@types/jsdom": "21.1.7", "@types/koa": "3.0.1", "@types/koa__multer": "2.0.7", - "@types/koa__router": "12.0.4", "@types/koa-bodyparser": "4.3.7", "@types/koa-json": "2.0.24", "@types/lowdb": "1.0.15", @@ -200,7 +199,7 @@ "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@koa/multer": "4.0.0", - "@koa/router": "14.0.0", + "@koa/router": "15.2.0", "big-integer": "1.6.52", "browser-hrtime": "1.1.8", "chalk": "4.1.2", @@ -8746,18 +8745,46 @@ } }, "node_modules/@koa/router": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@koa/router/-/router-14.0.0.tgz", - "integrity": "sha512-LBSu5K0qAaaQcXX/0WIB9PGDevyCxxpnc1uq13vV/CgObaVxuis5hKl3Eboq/8gcb6ebnkAStW9NB/Em2eYyFA==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-15.2.0.tgz", + "integrity": "sha512-7YUhq4W83cybfNa4E7JqJpWzoCTSvbnFltkvRaUaUX1ybFzlUoLNY1SqT8XmIAO6nGbFrev+FvJHw4mL+4WhuQ==", "license": "MIT", "dependencies": { - "debug": "^4.4.1", - "http-errors": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", "koa-compose": "^4.1.0", - "path-to-regexp": "^8.2.0" + "path-to-regexp": "^8.3.0" }, "engines": { "node": ">= 20" + }, + "peerDependencies": { + "koa": "^2.0.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "koa": { + "optional": false + } + } + }, + "node_modules/@koa/router/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/@leichtgewicht/ip-codec": { @@ -15735,16 +15762,6 @@ "@types/koa": "*" } }, - "node_modules/@types/koa__router": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/@types/koa__router/-/koa__router-12.0.4.tgz", - "integrity": "sha512-Y7YBbSmfXZpa/m5UGGzb7XadJIRBRnwNY9cdAojZGp65Cpe5MAP3mOZE7e3bImt8dfKS4UFcR16SLH8L/z7PBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/koa": "*" - } - }, "node_modules/@types/koa-bodyparser": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/@types/koa-bodyparser/-/koa-bodyparser-4.3.7.tgz", @@ -21479,9 +21496,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -35514,12 +35531,13 @@ } }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", - "engines": { - "node": ">=16" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/path-type": { diff --git a/package.json b/package.json index 2cc60c08c9d..ea7dc32820c 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,6 @@ "@types/jsdom": "21.1.7", "@types/koa": "3.0.1", "@types/koa__multer": "2.0.7", - "@types/koa__router": "12.0.4", "@types/koa-bodyparser": "4.3.7", "@types/koa-json": "2.0.24", "@types/lowdb": "1.0.15", @@ -167,7 +166,7 @@ "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", - "@koa/router": "14.0.0", + "@koa/router": "15.2.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "20.7.0", From 34108d93e40b2238931dc35089702df889aada0f Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:01:18 -0800 Subject: [PATCH 2/3] SSH Agent v2: Add ssh key primitive types (#18583) Co-authored-by: Bernd Schoolmann --- .github/CODEOWNERS | 4 +- apps/desktop/desktop_native/Cargo.lock | 16 +- apps/desktop/desktop_native/Cargo.toml | 1 + .../desktop_native/ssh_agent/Cargo.toml | 19 ++ .../ssh_agent/src/crypto/mod.rs | 184 ++++++++++++++++++ .../desktop_native/ssh_agent/src/lib.rs | 7 + 6 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 apps/desktop/desktop_native/ssh_agent/Cargo.toml create mode 100644 apps/desktop/desktop_native/ssh_agent/src/crypto/mod.rs create mode 100644 apps/desktop/desktop_native/ssh_agent/src/lib.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8bb15d37fdf..2582b96961d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -156,6 +156,8 @@ apps/desktop/macos/autofill-extension @bitwarden/team-autofill-desktop-dev apps/desktop/src/app/components/fido2placeholder.component.ts @bitwarden/team-autofill-desktop-dev apps/desktop/desktop_native/windows_plugin_authenticator @bitwarden/team-autofill-desktop-dev apps/desktop/desktop_native/autotype @bitwarden/team-autofill-desktop-dev +apps/desktop/desktop_native/core/src/ssh_agent @bitwarden/team-autofill-desktop-dev @bitwarden/wg-ssh-keys +apps/desktop/desktop_native/ssh_agent @bitwarden/team-autofill-desktop-dev @bitwarden/wg-ssh-keys apps/desktop/desktop_native/napi/src/autofill.rs @bitwarden/team-autofill-desktop-dev apps/desktop/desktop_native/napi/src/autotype.rs @bitwarden/team-autofill-desktop-dev apps/desktop/desktop_native/napi/src/sshagent.rs @bitwarden/team-autofill-desktop-dev @@ -164,8 +166,6 @@ apps/desktop/native-messaging-test-runner @bitwarden/team-autofill-desktop-dev apps/desktop/src/services/duckduckgo-message-handler.service.ts @bitwarden/team-autofill-desktop-dev apps/desktop/src/services/encrypted-message-handler.service.ts @bitwarden/team-autofill-desktop-dev .github/workflows/alert-ddg-files-modified.yml @bitwarden/team-autofill-desktop-dev -# SSH Agent -apps/desktop/desktop_native/core/src/ssh_agent @bitwarden/team-autofill-desktop-dev @bitwarden/wg-ssh-keys ## UI Foundation ## .github/workflows/chromatic.yml @bitwarden/team-ui-foundation diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index e5c197ef51c..7154c42ac89 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -377,9 +377,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "basic-toml" @@ -3295,6 +3295,9 @@ dependencies = [ "bcrypt-pbkdf", "ed25519-dalek", "num-bigint-dig", + "p256", + "p384", + "p521", "rand_core 0.6.4", "rsa", "sec1", @@ -3306,6 +3309,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ssh_agent" +version = "0.0.0" +dependencies = [ + "anyhow", + "base64", + "ssh-key", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index b3fac851026..09a4d603327 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -9,6 +9,7 @@ members = [ "napi", "process_isolation", "proxy", + "ssh_agent", "windows_plugin_authenticator", ] diff --git a/apps/desktop/desktop_native/ssh_agent/Cargo.toml b/apps/desktop/desktop_native/ssh_agent/Cargo.toml new file mode 100644 index 00000000000..becab28c356 --- /dev/null +++ b/apps/desktop/desktop_native/ssh_agent/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ssh_agent" +edition = { workspace = true } +license = { workspace = true } +version = { workspace = true } +publish = { workspace = true } + +[dependencies] +anyhow = { workspace = true } +base64 = { workspace = true } +ssh-key = { version = "=0.6.7", features = [ + "encryption", + "ed25519", + "rsa", + "rand_core", +] } + +[lints] +workspace = true diff --git a/apps/desktop/desktop_native/ssh_agent/src/crypto/mod.rs b/apps/desktop/desktop_native/ssh_agent/src/crypto/mod.rs new file mode 100644 index 00000000000..655e440dc78 --- /dev/null +++ b/apps/desktop/desktop_native/ssh_agent/src/crypto/mod.rs @@ -0,0 +1,184 @@ +//! Cryptographic key management for the SSH agent. +//! +//! This module provides the core primitive types and functionality for managing +//! SSH keys in the Bitwarden SSH agent. +//! +//! # Supported signing algorithms +//! +//! - Ed25519 +//! - RSA +//! +//! ECDSA keys are not currently supported (PM-29894) + +use std::fmt; + +use anyhow::anyhow; +use ssh_key::private::{Ed25519Keypair, RsaKeypair}; + +/// Represents an SSH key and its associated metadata. +#[derive(Clone)] +pub(crate) struct SSHKeyData { + /// Private key of the key pair + private_key: PrivateKey, + /// Public key of the key pair + public_key: PublicKey, + /// Human-readable name + name: String, + /// Vault cipher ID associated with the key pair + cipher_id: String, +} + +impl SSHKeyData { + /// Creates a new `SSHKeyData` instance. + /// + /// # Arguments + /// + /// * `private_key` - The private key component + /// * `public_key` - The public key component + /// * `name` - A human-readable name for the key + /// * `cipher_id` - The vault cipher identifier associated with this key + pub(crate) fn new( + private_key: PrivateKey, + public_key: PublicKey, + name: String, + cipher_id: String, + ) -> Self { + Self { + private_key, + public_key, + name, + cipher_id, + } + } + + /// # Returns + /// + /// A reference to the [`PublicKey`]. + pub(crate) fn public_key(&self) -> &PublicKey { + &self.public_key + } + + /// # Returns + /// + /// A reference to the [`PrivateKey`]. + pub(crate) fn private_key(&self) -> &PrivateKey { + &self.private_key + } + + /// # Returns + /// + /// A reference to the human-readable name for this key. + pub(crate) fn name(&self) -> &String { + &self.name + } + + /// # Returns + /// + /// A reference to the cipher ID that links this key to a vault entry. + pub(crate) fn cipher_id(&self) -> &String { + &self.cipher_id + } +} + +/// Represents an SSH private key. +#[derive(Clone, PartialEq, Debug)] +pub(crate) enum PrivateKey { + Ed25519(Ed25519Keypair), + Rsa(RsaKeypair), +} + +impl TryFrom for PrivateKey { + type Error = anyhow::Error; + + fn try_from(key: ssh_key::private::PrivateKey) -> Result { + match key.algorithm() { + ssh_key::Algorithm::Ed25519 => Ok(Self::Ed25519( + key.key_data() + .ed25519() + .ok_or(anyhow!("Failed to parse ed25519 key"))? + .to_owned(), + )), + ssh_key::Algorithm::Rsa { hash: _ } => Ok(Self::Rsa( + key.key_data() + .rsa() + .ok_or(anyhow!("Failed to parse RSA key"))? + .to_owned(), + )), + _ => Err(anyhow!("Unsupported key type")), + } + } +} + +/// Represents an SSH public key. +/// +/// Contains the algorithm identifier (e.g., "ssh-ed25519", "ssh-rsa") +/// and the binary blob of the public key data. +#[derive(Clone, Ord, Eq, PartialOrd, PartialEq)] +pub(crate) struct PublicKey { + pub alg: String, + pub blob: Vec, +} + +impl PublicKey { + pub(crate) fn alg(&self) -> &str { + &self.alg + } + + pub(crate) fn blob(&self) -> &[u8] { + &self.blob + } +} + +impl fmt::Debug for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PublicKey(\"{self}\")") + } +} + +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use base64::{prelude::BASE64_STANDARD, Engine as _}; + + write!(f, "{} {}", self.alg(), BASE64_STANDARD.encode(self.blob())) + } +} + +#[cfg(test)] +mod tests { + use ssh_key::{ + private::{Ed25519Keypair, RsaKeypair}, + rand_core::OsRng, + LineEnding, + }; + + use super::*; + + const MIN_KEY_BIT_SIZE: usize = 2048; + + fn create_valid_ed25519_key_string() -> String { + let ed25519_keypair = Ed25519Keypair::random(&mut OsRng); + let ssh_key = + ssh_key::PrivateKey::new(ssh_key::private::KeypairData::Ed25519(ed25519_keypair), "") + .unwrap(); + ssh_key.to_openssh(LineEnding::LF).unwrap().to_string() + } + + #[test] + fn test_privatekey_from_ed25519() { + let key_string = create_valid_ed25519_key_string(); + let ssh_key = ssh_key::PrivateKey::from_openssh(&key_string).unwrap(); + + let private_key = PrivateKey::try_from(ssh_key).unwrap(); + assert!(matches!(private_key, PrivateKey::Ed25519(_))); + } + + #[test] + fn test_privatekey_from_rsa() { + let rsa_keypair = RsaKeypair::random(&mut OsRng, MIN_KEY_BIT_SIZE).unwrap(); + let ssh_key = + ssh_key::PrivateKey::new(ssh_key::private::KeypairData::Rsa(rsa_keypair), "").unwrap(); + + let private_key = PrivateKey::try_from(ssh_key).unwrap(); + assert!(matches!(private_key, PrivateKey::Rsa(_))); + } +} diff --git a/apps/desktop/desktop_native/ssh_agent/src/lib.rs b/apps/desktop/desktop_native/ssh_agent/src/lib.rs new file mode 100644 index 00000000000..aaaf635e6c6 --- /dev/null +++ b/apps/desktop/desktop_native/ssh_agent/src/lib.rs @@ -0,0 +1,7 @@ +//! Bitwarden SSH Agent implementation +//! +//! + +#![allow(dead_code)] // TODO remove when all code is used in follow-up PR + +mod crypto; From 04d2394dbfeb9480456f89d0afcd0888e08a8b11 Mon Sep 17 00:00:00 2001 From: Vedant Madane <6527493+VedantMadane@users.noreply.github.com> Date: Thu, 5 Feb 2026 03:08:25 +0530 Subject: [PATCH 3/3] [PM-30845] fix(vault): preserve card brand when editing existing card (#18381) * fix(vault): preserve card brand when editing existing card Fixes #16978 The brand field was not being restored when editing an existing card cipher, causing it to show '--Select--' and potentially lose the brand data when saving. Added the brand field to initFromExistingCipher() to properly restore the card brand when opening a card for editing. Also updated the test to verify all card fields including brand, expMonth, and expYear are properly initialized from existing cipher data. * fix: add brand to OptionalInitialValues interface Addresses review feedback from @jengstrom-bw in PR #18381. The brand field was being used in card-details-section.component.ts but wasn't defined in the OptionalInitialValues type, causing a TypeScript compilation error. Adds brand?: string; to the Credit Card Information section of OptionalInitialValues in cipher-form-config.service.ts. * test: add coverage for initFromExistingCipher brand logic --- .../cipher-form-config.service.ts | 1 + .../card-details-section.component.spec.ts | 34 +++++++++++++++++-- .../card-details-section.component.ts | 1 + 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts b/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts index 35d3d8725ff..a4aabbb6f19 100644 --- a/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts +++ b/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts @@ -28,6 +28,7 @@ export type OptionalInitialValues = { // Credit Card Information cardholderName?: string; number?: string; + brand?: string; expMonth?: string; expYear?: string; code?: string; diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts index 4b0cd0f5f90..650b4e29fe5 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts @@ -108,12 +108,17 @@ describe("CardDetailsSectionComponent", () => { const cardholderName = "Ron Burgundy"; const number = "4242 4242 4242 4242"; const code = "619"; + const brand = "Maestro"; + const expMonth = "5"; + const expYear = "2028"; const cardView = new CardView(); cardView.cardholderName = cardholderName; cardView.number = number; cardView.code = code; - cardView.brand = "Visa"; + cardView.brand = brand; + cardView.expMonth = expMonth; + cardView.expYear = expYear; getInitialCipherView.mockReturnValueOnce({ card: cardView }); @@ -123,7 +128,9 @@ describe("CardDetailsSectionComponent", () => { cardholderName, number, code, - brand: cardView.brand, + brand, + expMonth, + expYear, }); }); @@ -154,4 +161,27 @@ describe("CardDetailsSectionComponent", () => { expect(heading.nativeElement.textContent.trim()).toBe("cardDetails"); }); + + it("initializes `cardDetailsForm` from `initialValues` when provided and editing existing cipher", () => { + const initialCardholderName = "New Name"; + const initialBrand = "Amex"; + + (cipherFormProvider as any).config = { + initialValues: { + cardholderName: initialCardholderName, + brand: initialBrand, + }, + }; + + const existingCard = new CardView(); + existingCard.cardholderName = "Old Name"; + existingCard.brand = "Visa"; + + getInitialCipherView.mockReturnValueOnce({ card: existingCard }); + + component.ngOnInit(); + + expect(component.cardDetailsForm.value.cardholderName).toBe(initialCardholderName); + expect(component.cardDetailsForm.value.brand).toBe(initialBrand); + }); }); diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts index 5fa8d0af131..056b93b6b99 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts @@ -158,6 +158,7 @@ export class CardDetailsSectionComponent implements OnInit { this.cardDetailsForm.patchValue({ cardholderName: this.initialValues?.cardholderName ?? existingCard.cardholderName, number: this.initialValues?.number ?? existingCard.number, + brand: this.initialValues?.brand ?? existingCard.brand, expMonth: this.initialValues?.expMonth ?? existingCard.expMonth, expYear: this.initialValues?.expYear ?? existingCard.expYear, code: this.initialValues?.code ?? existingCard.code,