Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/base/src/locale/en-US/dmsDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ export default {
returnModify: 'Back to Edit',
continueSubmit: 'Continue Submit'
},

passwordTransport: {
encryptFailed: 'Failed to encrypt password. Please try again later.'
},

testConnectModal: {
errorTitle: 'DB instance {{instanceName}} connectivity test failed'
},
Expand Down
4 changes: 4 additions & 0 deletions packages/base/src/locale/zh-CN/dmsDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ export default {
continueSubmit: '继续提交'
},

passwordTransport: {
encryptFailed: '口令加密失败,请稍后重试'
},

testConnectModal: {
errorTitle: '数据源{{instanceName}}连通性测试失败'
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ import EmitterKey from '../../../../data/EmitterKey';
import EventEmitter from '../../../../utils/EventEmitter';
import { mockProjectList } from '@actiontech/shared/lib/testUtil/mockApi/base/project/data';
import AddDataSource from '.';
import { encryptPasswordForTransport } from '../../../../utils/passwordTransportEncryption';

jest.mock('../../../../utils/passwordTransportEncryption', () => ({
encryptPasswordForTransport: jest.fn(async () => ({
secret_password: 'mocked-secret-password-b64'
})),
PasswordTransportError: class PasswordTransportError extends Error {
reason: string;
constructor(reason: string) {
super(reason);
this.reason = reason;
}
}
}));

jest.mock('react-router-dom', () => {
return {
Expand All @@ -37,6 +51,9 @@ describe('page/DataSource/AddDataSource', () => {

beforeEach(() => {
jest.useFakeTimers();
(encryptPasswordForTransport as jest.Mock).mockResolvedValue({
secret_password: 'mocked-secret-password-b64'
});
(useNavigate as jest.Mock).mockImplementation(() => navigateSpy);
baseMockApi.global.mockAllApi();
getProjectListSpy = baseMockApi.project.getProjectList();
Expand Down Expand Up @@ -178,7 +195,7 @@ describe('page/DataSource/AddDataSource', () => {
db_type: 'mysql',
host: '1.1.1.1',
user: 'root',
password: 'root',
secret_password: 'mocked-secret-password-b64',
port: '3306',
additional_params: [
{
Expand All @@ -205,7 +222,7 @@ describe('page/DataSource/AddDataSource', () => {
host: '1.1.1.1',
maintenance_times: [],
name: 'name-database',
password: 'root',
secret_password: 'mocked-secret-password-b64',
port: '3306',
sqle_config: {
audit_enabled: true,
Expand Down Expand Up @@ -431,7 +448,7 @@ describe('page/DataSource/AddDataSource', () => {
host: '1.1.1.1',
maintenance_times: [],
name: 'name-database',
password: 'root',
secret_password: 'mocked-secret-password-b64',
port: '3306',
sqle_config: {
audit_enabled: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { useBoolean } from 'ahooks';
import { useCallback } from 'react';
import { useForm } from 'antd/es/form/Form';
import { Space, Typography } from 'antd';
import { Space, Typography, message } from 'antd';
import { PageLayoutHasFixedHeaderStyleWrapper } from '@actiontech/dms-kit';
import {
BasicButton,
Expand All @@ -20,26 +20,53 @@
import { DmsApi } from '@actiontech/shared/lib/api';
import { DataSourceFormContextProvide } from '../../context';
import useCheckConnectable from '../../hooks/useCheckConnectable';
import {
encryptPasswordForTransport,
PasswordTransportError
} from '../../../../utils/passwordTransportEncryption';
const AddDataSource = () => {
const { t } = useTranslation();
const navigate = useTypedNavigate();
const [messageApi, messageContextHolder] = message.useMessage();
const [form] = useForm<DataSourceFormField>();
const { onCheckConnectable, loading, connectAble, connectErrorMessage } =
useCheckConnectable(form);
const [resultVisible, { setTrue: showResult, setFalse: hideResult }] =
useBoolean();
const [submitLoading, { setTrue: startSubmit, setFalse: submitFinish }] =
useBoolean();

const resolveTransportErrorMessage = useCallback(
(error: unknown) => {

Check warning on line 40 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
if (
error instanceof PasswordTransportError &&

Check warning on line 42 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
error.reason === 'encrypt_failed'

Check warning on line 43 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
) {
return t('dmsDataSource.passwordTransport.encryptFailed');

Check warning on line 45 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 46 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 46 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
return t('dmsDataSource.passwordTransport.encryptFailed');

Check warning on line 47 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
},
[t]
);

const addDatabase = async (values: DataSourceFormField) => {
startSubmit();
let cipher;
try {
cipher = await encryptPasswordForTransport(values.password ?? '');

Check warning on line 56 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
} catch (error) {
messageApi.error(resolveTransportErrorMessage(error));

Check warning on line 58 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
submitFinish();

Check warning on line 59 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
return;

Check warning on line 60 in packages/base/src/page/DataSource/components/AddDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}
const dbService: IDBServiceV2 = {
name: values.name,
desc: values.describe,
db_type: values.type,
host: values.ip,
port: values.port.toString(),
user: values.user,
password: values.password,
secret_password: cipher.secret_password,
environment_tag_uid: values.environmentTagId,
maintenance_times:
values.maintenanceTime?.map((time) => ({
Expand Down Expand Up @@ -104,6 +131,7 @@
};
return (
<PageLayoutHasFixedHeaderStyleWrapper>
{messageContextHolder}
<DataSourceFormContextProvide
value={{
loading,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ import {
mockProjectInfo
} from '@actiontech/shared/lib/testUtil';
import UpdateDataSource from '.';
import { encryptPasswordForTransport } from '../../../../utils/passwordTransportEncryption';

jest.mock('../../../../utils/passwordTransportEncryption', () => ({
encryptPasswordForTransport: jest.fn(async () => ({
secret_password: 'mocked-secret-password-b64'
})),
PasswordTransportError: class PasswordTransportError extends Error {
reason: string;
constructor(reason: string) {
super(reason);
this.reason = reason;
}
}
}));

jest.mock('react-router-dom', () => {
return {
Expand Down Expand Up @@ -42,6 +56,9 @@ describe('page/DataSource/UpdateDataSource', () => {

beforeEach(() => {
jest.useFakeTimers();
(encryptPasswordForTransport as jest.Mock).mockResolvedValue({
secret_password: 'mocked-secret-password-b64'
});
(useNavigate as jest.Mock).mockImplementation(() => navigateSpy);
jest.spyOn(Router, 'useParams').mockReturnValue({
dbServiceUid: uId
Expand Down Expand Up @@ -199,6 +216,10 @@ describe('page/DataSource/UpdateDataSource', () => {
await act(async () => jest.advanceTimersByTime(3000));
expect(updateDBServiceSpy).toHaveBeenCalledTimes(1);
const firstCallParams = updateDBServiceSpy.mock.calls[0][0];
expect(firstCallParams.db_service.password).toBeUndefined();
expect(firstCallParams.db_service.secret_password).toBe(
'mocked-secret-password-b64'
);
expect(
firstCallParams.db_service.sqle_config.sql_query_config
.workflow_exec_enabled
Expand Down Expand Up @@ -229,6 +250,8 @@ describe('page/DataSource/UpdateDataSource', () => {
await act(async () => jest.advanceTimersByTime(0));
expect(updateDBServiceSpy).toHaveBeenCalledTimes(1);
const params = updateDBServiceSpy.mock.calls[0][0];
expect(params.db_service.password).toBeUndefined();
expect(params.db_service.secret_password).toBeUndefined();
expect(
params.db_service.sqle_config.sql_query_config.workflow_exec_enabled
).toBe(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import { DmsApi } from '@actiontech/shared/lib/api';
import { DataSourceFormContextProvide } from '../../context';
import useCheckConnectable from '../../hooks/useCheckConnectable';
import {
encryptPasswordForTransport,
PasswordTransportError
} from '../../../../utils/passwordTransportEncryption';
const UpdateDataSource = () => {
const { t } = useTranslation();
const navigate = useTypedNavigate();
Expand All @@ -36,6 +40,20 @@
const [instanceInfo, setInstanceInfo] = useState<
IListDBServiceV2 | undefined
>();

const resolveTransportErrorMessage = useCallback(
(error: unknown) => {

Check warning on line 45 in packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
if (
error instanceof PasswordTransportError &&

Check warning on line 47 in packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
error.reason === 'encrypt_failed'

Check warning on line 48 in packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
) {
return t('dmsDataSource.passwordTransport.encryptFailed');

Check warning on line 50 in packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 51 in packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 51 in packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
return t('dmsDataSource.passwordTransport.encryptFailed');

Check warning on line 52 in packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
},
[t]
);

const updateDatabase = async (values: DataSourceFormField) => {
startSubmit();
const params: IUpdateDBServiceV2Params = {
Expand Down Expand Up @@ -91,7 +109,15 @@
// #endif

if (!!values.needUpdatePassword && !!values.password && params.db_service) {
params.db_service.password = values.password;
let cipher;
try {

Check warning on line 113 in packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
cipher = await encryptPasswordForTransport(values.password);

Check warning on line 114 in packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
} catch (error) {

Check warning on line 115 in packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
messageApi.error(resolveTransportErrorMessage(error));
submitFinish();
return;
}
params.db_service.secret_password = cipher.secret_password;
}
return DmsApi.DBServiceService.UpdateDBServiceV2(params)
.then((res) => {
Expand Down
40 changes: 38 additions & 2 deletions packages/base/src/page/DataSource/hooks/useCheckConnectable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useState } from 'react';
import { Form, FormInstance } from 'antd';
import { useTranslation } from 'react-i18next';
import { DataSourceFormField } from '../components/Form/index.type';
import { useBoolean } from 'ahooks';
import { DmsApi } from '@actiontech/shared/lib/api/';
Expand All @@ -10,8 +11,13 @@
getDbServiceIsConnectbale
} from '../../../utils/common';
import { mergeRedisConnectionModeIntoParams } from '../tool';
import {
encryptPasswordForTransport,
PasswordTransportError
} from '../../../utils/passwordTransportEncryption';

const useCheckConnectable = (form: FormInstance<DataSourceFormField>) => {
const { t } = useTranslation();
const projectID = Form.useWatch('project', form);

const [loading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] =
Expand All @@ -21,6 +27,19 @@

const { mergeFromValueIntoParams } = useAsyncParams();

const resolveTransportErrorMessage = useCallback(
(error: unknown) => {

Check warning on line 31 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
if (
error instanceof PasswordTransportError &&

Check warning on line 33 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
error.reason === 'encrypt_failed'

Check warning on line 34 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
) {
return t('dmsDataSource.passwordTransport.encryptFailed');

Check warning on line 36 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 37 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 37 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
return t('dmsDataSource.passwordTransport.encryptFailed');

Check warning on line 38 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
},
[t]
);

const onCheckConnectable = useCallback(
async (currentAsyncParams?: BackendFormItemParams[]) => {
const values = await form.validateFields([
Expand Down Expand Up @@ -48,13 +67,23 @@
);

setLoadingTrue();
let cipher;
try {
cipher = await encryptPasswordForTransport(values.password ?? '');

Check warning on line 72 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
} catch (error) {
setConnectAble(false);

Check warning on line 74 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
setConnectErrorMessage(resolveTransportErrorMessage(error));

Check warning on line 75 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
setLoadingFalse();

Check warning on line 76 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
return false;

Check warning on line 77 in packages/base/src/page/DataSource/hooks/useCheckConnectable.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

return DmsApi.DBServiceService.CheckDBServiceIsConnectable({
db_service: {
host: values.ip,
port: `${values.port}`,
user: values.user,
db_type: values.type,
password: values.password,
secret_password: cipher.secret_password,
additional_params: values.asyncParams ?? []
},
project_uid: projectID
Expand All @@ -75,7 +104,14 @@
setLoadingFalse();
});
},
[form, projectID, mergeFromValueIntoParams, setLoadingTrue, setLoadingFalse]
[
form,
projectID,
mergeFromValueIntoParams,
setLoadingTrue,
setLoadingFalse,
resolveTransportErrorMessage
]
);

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @jest-environment node
*/
import {
encryptPasswordForTransport,
PASSWORD_TRANSPORT_SECRET_KEY
} from '../passwordTransportEncryption';

describe('passwordTransportEncryption (fixed AES)', () => {
it('uses the backend compile-time SecretKey constant', () => {
expect(PASSWORD_TRANSPORT_SECRET_KEY).toBe(
'471F77D078C5994BD06B65B8B5B1935B'
);
});

it('encrypts to Base64 AES-256-CBC matching Go pkg/aes', async () => {
// Reference ciphertexts from dms-ee pkg/aes NewEncryptor(SecretKey).AesEncrypt
await expect(encryptPasswordForTransport('test')).resolves.toEqual({
secret_password: 'MbFLdPIZEb/G2QAHPMxSuQ=='
});
await expect(encryptPasswordForTransport('admin')).resolves.toEqual({
secret_password: 'lvwmEx6EhCnyMw9cEOKIXQ=='
});
});

it('returns only secret_password (no encryption_key_id)', async () => {
const cipher = await encryptPasswordForTransport('hello');
expect(cipher).toEqual({
secret_password: 'aFPI8tYp2iiuN/6Kk30TZw=='
});
expect(Object.keys(cipher)).toEqual(['secret_password']);
});
});
Loading
Loading