Skip to content

Commit b75a236

Browse files
authored
Merge pull request #2808 from RedisInsight/feature/RI-5190_hide_sentinel_password
#RI-5190 - hide sentinel password
2 parents 215fa83 + b045170 commit b75a236

File tree

14 files changed

+142
-21
lines changed

14 files changed

+142
-21
lines changed

redisinsight/api/src/modules/database/dto/database.response.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { ApiPropertyOptional, OmitType } from '@nestjs/swagger';
22
import { Database } from 'src/modules/database/models/database';
3-
import { SshOptionsResponse } from 'src/modules/ssh/dto/ssh-options.response.';
3+
import { SshOptionsResponse } from 'src/modules/ssh/dto/ssh-options.response';
4+
import { SentinelMasterResponse } from 'src/modules/redis-sentinel/dto/sentinel.master.response.dto';
45
import { Expose, Type } from 'class-transformer';
56
import { HiddenField } from 'src/common/decorators/hidden-field.decorator';
67

7-
export class DatabaseResponse extends OmitType(Database, ['password', 'sshOptions'] as const) {
8+
export class DatabaseResponse extends OmitType(Database, ['password', 'sshOptions', 'sentinelMaster'] as const) {
89
@ApiPropertyOptional({
910
description: 'The database password flag (true if password was set)',
1011
type: Boolean,
@@ -20,4 +21,12 @@ export class DatabaseResponse extends OmitType(Database, ['password', 'sshOption
2021
@Expose()
2122
@Type(() => SshOptionsResponse)
2223
sshOptions?: SshOptionsResponse;
24+
25+
@ApiPropertyOptional({
26+
description: 'Sentinel master',
27+
type: SentinelMasterResponse,
28+
})
29+
@Expose()
30+
@Type(() => SentinelMasterResponse)
31+
sentinelMaster?: SentinelMasterResponse;
2332
}

redisinsight/api/src/modules/database/dto/update.database.dto.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import {
66
IsInt, IsNotEmpty, IsNotEmptyObject, IsOptional, Max, Min, ValidateNested, ValidateIf, IsString, MaxLength,
77
} from 'class-validator';
88
import { UpdateSshOptionsDto } from 'src/modules/ssh/dto/update.ssh-options.dto';
9+
import { UpdateSentinelMasterDto } from 'src/modules/redis-sentinel/dto/update.sentinel.master.dto';
910
import { CreateDatabaseDto } from 'src/modules/database/dto/create.database.dto';
1011

1112
export class UpdateDatabaseDto extends PartialType(OmitType(CreateDatabaseDto, [
12-
'sshOptions', 'timeout',
13+
'sshOptions', 'timeout', 'sentinelMaster',
1314
] as const)) {
1415
@ValidateIf((object, value) => value !== undefined)
1516
@IsString({ always: true })
@@ -45,4 +46,14 @@ export class UpdateDatabaseDto extends PartialType(OmitType(CreateDatabaseDto, [
4546
@Max(1_000_000_000)
4647
@IsInt({ always: true })
4748
timeout?: number;
49+
50+
@ApiPropertyOptional({
51+
description: 'Updated sentinel master fields',
52+
})
53+
@Expose()
54+
@IsOptional()
55+
@IsNotEmptyObject()
56+
@Type(() => UpdateSentinelMasterDto)
57+
@ValidateNested()
58+
sentinelMaster?: UpdateSentinelMasterDto;
4859
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ApiPropertyOptional, OmitType } from '@nestjs/swagger';
2+
import { SentinelMaster } from 'src/modules/redis-sentinel/models/sentinel-master';
3+
import { Expose } from 'class-transformer';
4+
import { HiddenField } from 'src/common/decorators/hidden-field.decorator';
5+
6+
export class SentinelMasterResponse extends OmitType(SentinelMaster, ['password'] as const) {
7+
@ApiPropertyOptional({
8+
description:
9+
'The password for your Redis Sentinel master. '
10+
+ 'If your master doesn’t require a password, leave this field empty.',
11+
type: Boolean,
12+
})
13+
@Expose()
14+
@HiddenField(true)
15+
password?: boolean;
16+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { PickType } from '@nestjs/swagger';
2+
import { SentinelMaster } from 'src/modules/redis-sentinel/models/sentinel-master';
3+
4+
export class UpdateSentinelMasterDto extends PickType(SentinelMaster, ['username', 'password'] as const) {}
File renamed without changes.

redisinsight/api/test/api/database/PATCH-databases-id.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,4 +920,47 @@ describe(`PATCH /databases/:id`, () => {
920920
});
921921
});
922922
});
923+
924+
describe('SENTINEL', () => {
925+
describe('PASS', function () {
926+
requirements('rte.type=SENTINEL', '!rte.tls', 'rte.pass');
927+
it('Should update database without full sentinel master information', async () => {
928+
const dbName = constants.getRandomString();
929+
930+
expect(await localDb.getInstanceByName(dbName)).to.eql(null);
931+
932+
await validateApiCall({
933+
endpoint,
934+
data: {
935+
name: dbName,
936+
sentinelMaster: {
937+
password: constants.TEST_SENTINEL_MASTER_PASS || null,
938+
},
939+
},
940+
});
941+
942+
expect(await localDb.getInstanceByName(dbName)).to.be.an('object');
943+
});
944+
945+
it('Should throw Unauthorized error', async () => {
946+
const dbName = constants.getRandomString();
947+
948+
await validateApiCall({
949+
endpoint,
950+
statusCode: 401,
951+
data: {
952+
name: dbName,
953+
sentinelMaster: {
954+
password: 'incorrect password'
955+
},
956+
},
957+
responseBody: {
958+
statusCode: 401,
959+
message: 'Failed to authenticate, please check the username or password.',
960+
error: 'Unauthorized'
961+
},
962+
});
963+
});
964+
});
965+
});
923966
});

redisinsight/api/test/api/database/POST-databases-test-id.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,4 +563,42 @@ describe(`POST /databases/test/:id`, () => {
563563
});
564564
});
565565
});
566+
describe('SENTINEL', () => {
567+
describe('PASS', function () {
568+
requirements('rte.type=SENTINEL', '!rte.tls', 'rte.pass');
569+
it('Should test connection without full sentinel master information', async () => {
570+
const dbName = constants.getRandomString();
571+
572+
await validateApiCall({
573+
endpoint,
574+
data: {
575+
name: dbName,
576+
sentinelMaster: {
577+
password: constants.TEST_SENTINEL_MASTER_PASS || null,
578+
},
579+
},
580+
});
581+
});
582+
583+
it('Should throw Unauthorized error', async () => {
584+
const dbName = constants.getRandomString();
585+
586+
await validateApiCall({
587+
endpoint,
588+
statusCode: 401,
589+
data: {
590+
name: dbName,
591+
sentinelMaster: {
592+
password: 'incorrect password'
593+
},
594+
},
595+
responseBody: {
596+
statusCode: 401,
597+
message: 'Failed to authenticate, please check the username or password.',
598+
error: 'Unauthorized'
599+
},
600+
});
601+
});
602+
});
603+
});
566604
});

redisinsight/api/test/api/database/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const databaseSchema = Joi.object().keys({
2424
sentinelMaster: Joi.object({
2525
name: Joi.string().required(),
2626
username: Joi.string().allow(null),
27-
password: Joi.string().allow(null),
27+
password: Joi.boolean().allow(null),
2828
}).allow(null),
2929
nodes: Joi.array().items({
3030
host: Joi.string().required(),

redisinsight/ui/src/pages/home/components/form/DatabaseForm.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
validatePortNumber,
2424
validateTimeoutNumber,
2525
} from 'uiSrc/utils'
26-
import { DbConnectionInfo, IPasswordType } from 'uiSrc/pages/home/interfaces'
26+
import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces'
2727

2828
interface IShowFields {
2929
alias: boolean
@@ -39,7 +39,6 @@ export interface Props {
3939
onHostNamePaste: (content: string) => boolean
4040
showFields: IShowFields
4141
autoFocus?: boolean
42-
passwordType?: IPasswordType
4342
}
4443

4544
const DatabaseForm = (props: Props) => {
@@ -50,7 +49,6 @@ const DatabaseForm = (props: Props) => {
5049
onHostNamePaste,
5150
autoFocus = false,
5251
showFields,
53-
passwordType = IPasswordType.Password,
5452
} = props
5553

5654
const { server } = useSelector(appInfoSelector)
@@ -185,7 +183,7 @@ const DatabaseForm = (props: Props) => {
185183
<EuiFlexItem className={flexItemClassName}>
186184
<EuiFormRow label="Password">
187185
<EuiFieldPassword
188-
type={passwordType}
186+
type="password"
189187
name="password"
190188
id="password"
191189
data-testid="password"

redisinsight/ui/src/pages/home/components/form/sentinel/SentinelMasterDatabase.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import {
1111
import { FormikProps } from 'formik'
1212

1313
import { Nullable } from 'uiSrc/utils'
14+
import { SECURITY_FIELD } from 'uiSrc/constants'
1415
import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces'
16+
1517
import styles from '../../styles.module.scss'
1618

1719
export interface Props {
@@ -53,16 +55,24 @@ const SentinelMasterDatabase = (props: Props) => {
5355
<EuiFlexItem className={flexItemClassName}>
5456
<EuiFormRow label="Password">
5557
<EuiFieldPassword
56-
type="dual"
58+
type="password"
5759
name="sentinelMasterPassword"
5860
id="sentinelMasterPassword"
5961
data-testid="sentinel-master-password"
6062
fullWidth
6163
className="passwordField"
6264
maxLength={200}
6365
placeholder="Enter Password"
64-
value={formik.values.sentinelMasterPassword ?? ''}
66+
value={formik.values.sentinelMasterPassword === true ? SECURITY_FIELD : formik.values.sentinelMasterPassword ?? ''}
6567
onChange={formik.handleChange}
68+
onFocus={() => {
69+
if (formik.values.sentinelMasterPassword === true) {
70+
formik.setFieldValue(
71+
'sentinelMasterPassword',
72+
'',
73+
)
74+
}
75+
}}
6676
dualToggleProps={{ color: 'text' }}
6777
autoComplete="new-password"
6878
/>

0 commit comments

Comments
 (0)