From bde41776b95d295f8d79aebbbb3ebb4e5b4932dc Mon Sep 17 00:00:00 2001 From: ivinokur Date: Wed, 31 Jul 2024 14:46:03 +0300 Subject: [PATCH 1/2] Add passphrase input to the add SSH key dialog --- packages/common/src/dto/api/index.ts | 1 + .../sshKeysApi/__tests__/helpers.spec.ts | 28 +++++++ .../services/sshKeysApi/helpers.ts | 2 + .../__snapshots__/index.spec.tsx.snap | 48 ++++++++++++ .../SshPassphrase/__tests__/index.spec.tsx | 76 +++++++++++++++++++ .../AddModal/Form/SshPassphrase/index.tsx | 51 +++++++++++++ .../SshKeys/AddModal/Form/index.tsx | 19 ++++- 7 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/__snapshots__/index.spec.tsx.snap create mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/index.spec.tsx create mode 100644 packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/index.tsx diff --git a/packages/common/src/dto/api/index.ts b/packages/common/src/dto/api/index.ts index afc7e1bfc..cb44ec1d9 100644 --- a/packages/common/src/dto/api/index.ts +++ b/packages/common/src/dto/api/index.ts @@ -58,6 +58,7 @@ export type SshKey = { * base64 encoded public key. */ keyPub: string; + passphrase?: string; }; export type NewSshKey = Omit & { /** diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/__tests__/helpers.spec.ts b/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/__tests__/helpers.spec.ts index 052c0e649..8f5c3355a 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/__tests__/helpers.spec.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/__tests__/helpers.spec.ts @@ -74,6 +74,7 @@ describe('Helpers for SSH Keys API', () => { 'dwo_ssh_key.pub': btoa('ssh-key-pub-data'), dwo_ssh_key: btoa('ssh-key-data'), ssh_config: btoa('git-config'), + passphrase: '', }, }; @@ -125,4 +126,31 @@ describe('Helpers for SSH Keys API', () => { } as SshKeySecret); }); }); + test('SSH key with passphrase', () => { + const namespace = 'user-che'; + const token: api.NewSshKey = { + name: 'git-ssh-key', + key: 'ssh-key-data', + keyPub: 'ssh-key-pub-data', + passphrase: 'passphrase', + }; + + const secret = toSecret(namespace, token); + expect(secret).toStrictEqual({ + apiVersion: 'v1', + data: { + 'dwo_ssh_key.pub': token.keyPub, + dwo_ssh_key: token.key, + ssh_config: btoa(SSH_CONFIG), + passphrase: 'passphrase', + }, + kind: 'Secret', + metadata: { + annotations: SSH_SECRET_ANNOTATIONS, + labels: SSH_SECRET_LABELS, + name: 'git-ssh-key', + namespace: 'user-che', + }, + } as SshKeySecret); + }); }); diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/helpers.ts b/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/helpers.ts index b76ba6007..6ccec5354 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/helpers.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/helpers.ts @@ -38,6 +38,7 @@ export interface SshKeySecret extends k8s.V1Secret { data: { dwo_ssh_key: string; 'dwo_ssh_key.pub': string; + passphrase?: string; ssh_config: string; }; } @@ -88,6 +89,7 @@ export function toSecret(namespace: string, sshKey: api.NewSshKey): SshKeySecret 'dwo_ssh_key.pub': sshKey.keyPub, dwo_ssh_key: sshKey.key, ssh_config: btoa(SSH_CONFIG), + ...(sshKey.passphrase && { passphrase: sshKey.passphrase }), }, }; } diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/__snapshots__/index.spec.tsx.snap new file mode 100644 index 000000000..9dce7c255 --- /dev/null +++ b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/__snapshots__/index.spec.tsx.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SshPassphrase snapshot 1`] = ` +
+
+
+ + +
+
+ + +
+
+
+`; diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/index.spec.tsx new file mode 100644 index 000000000..8efb588da --- /dev/null +++ b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/index.spec.tsx @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { Form } from '@patternfly/react-core'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { SshPassphrase } from '@/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase'; +import getComponentRenderer, { screen } from '@/services/__mocks__/getComponentRenderer'; + +const { createSnapshot, renderComponent } = getComponentRenderer(getComponent); + +const mockOnChange = jest.fn(); + +jest.mock('@/components/TextFileUpload'); + +describe('SshPassphrase', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('snapshot', () => { + const snapshot = createSnapshot(); + expect(snapshot.toJSON()).toMatchSnapshot(); + }); + + describe('text area', () => { + it('should handle SSH passphrase', () => { + renderComponent(); + + expect(mockOnChange).not.toHaveBeenCalled(); + + const input = screen.getByPlaceholderText('Enter passphrase (optional)'); + + const passphrase = 'passphrase'; + userEvent.paste(input, passphrase); + + expect(mockOnChange).toHaveBeenCalledWith(passphrase); + }); + + it('should handle the empty value', () => { + renderComponent(); + + expect(mockOnChange).not.toHaveBeenCalled(); + + const input = screen.getByPlaceholderText('Enter passphrase (optional)'); + + // fill the SSH key data field + userEvent.paste(input, 'ssh-key-data'); + + mockOnChange.mockClear(); + + // clear the SSH key data field + userEvent.clear(input); + + expect(mockOnChange).toHaveBeenCalledWith(''); + }); + }); +}); + +function getComponent(): React.ReactElement { + return ( +
+ + + ); +} diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/index.tsx new file mode 100644 index 000000000..8ccd392e4 --- /dev/null +++ b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/index.tsx @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { FormGroup, TextInput } from '@patternfly/react-core'; +import React from 'react'; + +export type Props = { + onChange: (passphrase: string) => void; +}; + +export type State = { + passphrase: string | undefined; +}; + +export class SshPassphrase extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + passphrase: undefined, + }; + } + + private onChange(passphrase: string): void { + const { onChange } = this.props; + + this.setState({ passphrase }); + onChange(passphrase); + } + + public render(): React.ReactElement { + return ( + + this.onChange(passphrase)} + /> + + ); + } +} diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/index.tsx index c8f0adb14..7eaddf253 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/index.tsx +++ b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/index.tsx @@ -14,6 +14,7 @@ import { api } from '@eclipse-che/common'; import { Form } from '@patternfly/react-core'; import React from 'react'; +import { SshPassphrase } from '@/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase'; import { SshPrivateKey } from '@/pages/UserPreferences/SshKeys/AddModal/Form/SshPrivateKey'; import { SshPublicKey } from '@/pages/UserPreferences/SshKeys/AddModal/Form/SshPublicKey'; @@ -27,6 +28,7 @@ export type State = { privateKeyIsValid: boolean; publicKey: string | undefined; publicKeyIsValid: boolean; + passphrase: string | undefined; }; export class AddModalForm extends React.PureComponent { @@ -38,6 +40,7 @@ export class AddModalForm extends React.PureComponent { privateKeyIsValid: false, publicKey: undefined, publicKeyIsValid: false, + passphrase: undefined, }; } @@ -45,12 +48,19 @@ export class AddModalForm extends React.PureComponent { const nextState = { ...this.state, ...partialState }; this.setState(nextState); - const { privateKey = '', privateKeyIsValid, publicKey = '', publicKeyIsValid } = nextState; + const { + privateKey = '', + privateKeyIsValid, + publicKey = '', + publicKeyIsValid, + passphrase = '', + } = nextState; const newSshKey: api.NewSshKey = { name: SSH_KEY_NAME, key: privateKey, keyPub: publicKey, + passphrase: passphrase, }; const isValid = privateKeyIsValid && publicKeyIsValid; this.props.onChange(newSshKey, isValid); @@ -70,11 +80,18 @@ export class AddModalForm extends React.PureComponent { }); } + private handleChangeSshPassphrase(passphrase: string): void { + this.updateChangeSshKey({ + passphrase, + }); + } + public render(): React.ReactElement { return (
e.preventDefault()}> this.handleChangeSshPrivateKey(...args)} /> this.handleChangeSshPublicKey(...args)} /> + this.handleChangeSshPassphrase(...args)} /> ); } From 442020218749390ee39e2f8f920f9c114079d4b8 Mon Sep 17 00:00:00 2001 From: ivinokur Date: Tue, 6 Aug 2024 10:18:57 +0300 Subject: [PATCH 2/2] fixup! Add passphrase input to the add SSH key dialog --- .../services/sshKeysApi/__tests__/helpers.spec.ts | 2 +- .../src/devworkspaceClient/services/sshKeysApi/helpers.ts | 2 +- .../SshPassphrase/__tests__/__snapshots__/index.spec.tsx.snap | 2 +- .../SshKeys/AddModal/Form/SshPassphrase/index.tsx | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/__tests__/helpers.spec.ts b/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/__tests__/helpers.spec.ts index 8f5c3355a..4b769a4a3 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/__tests__/helpers.spec.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/__tests__/helpers.spec.ts @@ -142,7 +142,7 @@ describe('Helpers for SSH Keys API', () => { 'dwo_ssh_key.pub': token.keyPub, dwo_ssh_key: token.key, ssh_config: btoa(SSH_CONFIG), - passphrase: 'passphrase', + passphrase: btoa('passphrase'), }, kind: 'Secret', metadata: { diff --git a/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/helpers.ts b/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/helpers.ts index 6ccec5354..20c77a2d0 100644 --- a/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/helpers.ts +++ b/packages/dashboard-backend/src/devworkspaceClient/services/sshKeysApi/helpers.ts @@ -89,7 +89,7 @@ export function toSecret(namespace: string, sshKey: api.NewSshKey): SshKeySecret 'dwo_ssh_key.pub': sshKey.keyPub, dwo_ssh_key: sshKey.key, ssh_config: btoa(SSH_CONFIG), - ...(sshKey.passphrase && { passphrase: sshKey.passphrase }), + ...(sshKey.passphrase && { passphrase: btoa(sshKey.passphrase) }), }, }; } diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/__snapshots__/index.spec.tsx.snap b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/__snapshots__/index.spec.tsx.snap index 9dce7c255..e50dfd19f 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/__snapshots__/index.spec.tsx.snap +++ b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/__tests__/__snapshots__/index.spec.tsx.snap @@ -39,7 +39,7 @@ exports[`SshPassphrase snapshot 1`] = ` onFocus={[Function]} placeholder="Enter passphrase (optional)" required={false} - type="text" + type="password" /> diff --git a/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/index.tsx b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/index.tsx index 8ccd392e4..6c58858c6 100644 --- a/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/index.tsx +++ b/packages/dashboard-frontend/src/pages/UserPreferences/SshKeys/AddModal/Form/SshPassphrase/index.tsx @@ -44,6 +44,7 @@ export class SshPassphrase extends React.Component { aria-label="Passphrase" placeholder="Enter passphrase (optional)" onChange={passphrase => this.onChange(passphrase)} + type="password" /> );