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
13 changes: 11 additions & 2 deletions redisinsight/api/src/modules/database/dto/database.response.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ApiPropertyOptional, OmitType } from '@nestjs/swagger';
import { Database } from 'src/modules/database/models/database';
import { SshOptionsResponse } from 'src/modules/ssh/dto/ssh-options.response.';
import { SshOptionsResponse } from 'src/modules/ssh/dto/ssh-options.response';
import { SentinelMasterResponse } from 'src/modules/redis-sentinel/dto/sentinel.master.response.dto';
import { Expose, Type } from 'class-transformer';
import { HiddenField } from 'src/common/decorators/hidden-field.decorator';

export class DatabaseResponse extends OmitType(Database, ['password', 'sshOptions'] as const) {
export class DatabaseResponse extends OmitType(Database, ['password', 'sshOptions', 'sentinelMaster'] as const) {
@ApiPropertyOptional({
description: 'The database password flag (true if password was set)',
type: Boolean,
Expand All @@ -20,4 +21,12 @@ export class DatabaseResponse extends OmitType(Database, ['password', 'sshOption
@Expose()
@Type(() => SshOptionsResponse)
sshOptions?: SshOptionsResponse;

@ApiPropertyOptional({
description: 'Sentinel master',
type: SentinelMasterResponse,
})
@Expose()
@Type(() => SentinelMasterResponse)
sentinelMaster?: SentinelMasterResponse;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import {
IsInt, IsNotEmpty, IsNotEmptyObject, IsOptional, Max, Min, ValidateNested, ValidateIf, IsString, MaxLength,
} from 'class-validator';
import { UpdateSshOptionsDto } from 'src/modules/ssh/dto/update.ssh-options.dto';
import { UpdateSentinelMasterDto } from 'src/modules/redis-sentinel/dto/update.sentinel.master.dto';
import { CreateDatabaseDto } from 'src/modules/database/dto/create.database.dto';

export class UpdateDatabaseDto extends PartialType(OmitType(CreateDatabaseDto, [
'sshOptions', 'timeout',
'sshOptions', 'timeout', 'sentinelMaster',
] as const)) {
@ValidateIf((object, value) => value !== undefined)
@IsString({ always: true })
Expand Down Expand Up @@ -45,4 +46,14 @@ export class UpdateDatabaseDto extends PartialType(OmitType(CreateDatabaseDto, [
@Max(1_000_000_000)
@IsInt({ always: true })
timeout?: number;

@ApiPropertyOptional({
description: 'Updated sentinel master fields',
})
@Expose()
@IsOptional()
@IsNotEmptyObject()
@Type(() => UpdateSentinelMasterDto)
@ValidateNested()
sentinelMaster?: UpdateSentinelMasterDto;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ApiPropertyOptional, OmitType } from '@nestjs/swagger';
import { SentinelMaster } from 'src/modules/redis-sentinel/models/sentinel-master';
import { Expose } from 'class-transformer';
import { HiddenField } from 'src/common/decorators/hidden-field.decorator';

export class SentinelMasterResponse extends OmitType(SentinelMaster, ['password'] as const) {
@ApiPropertyOptional({
description:
'The password for your Redis Sentinel master. '
+ 'If your master doesn’t require a password, leave this field empty.',
type: Boolean,
})
@Expose()
@HiddenField(true)
password?: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PickType } from '@nestjs/swagger';
import { SentinelMaster } from 'src/modules/redis-sentinel/models/sentinel-master';

export class UpdateSentinelMasterDto extends PickType(SentinelMaster, ['username', 'password'] as const) {}
43 changes: 43 additions & 0 deletions redisinsight/api/test/api/database/PATCH-databases-id.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -920,4 +920,47 @@ describe(`PATCH /databases/:id`, () => {
});
});
});

describe('SENTINEL', () => {
describe('PASS', function () {
requirements('rte.type=SENTINEL', '!rte.tls', 'rte.pass');
it('Should update database without full sentinel master information', async () => {
const dbName = constants.getRandomString();

expect(await localDb.getInstanceByName(dbName)).to.eql(null);

await validateApiCall({
endpoint,
data: {
name: dbName,
sentinelMaster: {
password: constants.TEST_SENTINEL_MASTER_PASS || null,
},
},
});

expect(await localDb.getInstanceByName(dbName)).to.be.an('object');
});

it('Should throw Unauthorized error', async () => {
const dbName = constants.getRandomString();

await validateApiCall({
endpoint,
statusCode: 401,
data: {
name: dbName,
sentinelMaster: {
password: 'incorrect password'
},
},
responseBody: {
statusCode: 401,
message: 'Failed to authenticate, please check the username or password.',
error: 'Unauthorized'
},
});
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -563,4 +563,42 @@ describe(`POST /databases/test/:id`, () => {
});
});
});
describe('SENTINEL', () => {
describe('PASS', function () {
requirements('rte.type=SENTINEL', '!rte.tls', 'rte.pass');
it('Should test connection without full sentinel master information', async () => {
const dbName = constants.getRandomString();

await validateApiCall({
endpoint,
data: {
name: dbName,
sentinelMaster: {
password: constants.TEST_SENTINEL_MASTER_PASS || null,
},
},
});
});

it('Should throw Unauthorized error', async () => {
const dbName = constants.getRandomString();

await validateApiCall({
endpoint,
statusCode: 401,
data: {
name: dbName,
sentinelMaster: {
password: 'incorrect password'
},
},
responseBody: {
statusCode: 401,
message: 'Failed to authenticate, please check the username or password.',
error: 'Unauthorized'
},
});
});
});
});
});
2 changes: 1 addition & 1 deletion redisinsight/api/test/api/database/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const databaseSchema = Joi.object().keys({
sentinelMaster: Joi.object({
name: Joi.string().required(),
username: Joi.string().allow(null),
password: Joi.string().allow(null),
password: Joi.boolean().allow(null),
}).allow(null),
nodes: Joi.array().items({
host: Joi.string().required(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
validatePortNumber,
validateTimeoutNumber,
} from 'uiSrc/utils'
import { DbConnectionInfo, IPasswordType } from 'uiSrc/pages/home/interfaces'
import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces'

interface IShowFields {
alias: boolean
Expand All @@ -39,7 +39,6 @@ export interface Props {
onHostNamePaste: (content: string) => boolean
showFields: IShowFields
autoFocus?: boolean
passwordType?: IPasswordType
}

const DatabaseForm = (props: Props) => {
Expand All @@ -50,7 +49,6 @@ const DatabaseForm = (props: Props) => {
onHostNamePaste,
autoFocus = false,
showFields,
passwordType = IPasswordType.Password,
} = props

const { server } = useSelector(appInfoSelector)
Expand Down Expand Up @@ -185,7 +183,7 @@ const DatabaseForm = (props: Props) => {
<EuiFlexItem className={flexItemClassName}>
<EuiFormRow label="Password">
<EuiFieldPassword
type={passwordType}
type="password"
name="password"
id="password"
data-testid="password"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {
import { FormikProps } from 'formik'

import { Nullable } from 'uiSrc/utils'
import { SECURITY_FIELD } from 'uiSrc/constants'
import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces'

import styles from '../../styles.module.scss'

export interface Props {
Expand Down Expand Up @@ -53,16 +55,24 @@ const SentinelMasterDatabase = (props: Props) => {
<EuiFlexItem className={flexItemClassName}>
<EuiFormRow label="Password">
<EuiFieldPassword
type="dual"
type="password"
name="sentinelMasterPassword"
id="sentinelMasterPassword"
data-testid="sentinel-master-password"
fullWidth
className="passwordField"
maxLength={200}
placeholder="Enter Password"
value={formik.values.sentinelMasterPassword ?? ''}
value={formik.values.sentinelMasterPassword === true ? SECURITY_FIELD : formik.values.sentinelMasterPassword ?? ''}
onChange={formik.handleChange}
onFocus={() => {
if (formik.values.sentinelMasterPassword === true) {
formik.setFieldValue(
'sentinelMasterPassword',
'',
)
}
}}
dualToggleProps={{ color: 'text' }}
autoComplete="new-password"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
fieldDisplayNames,
} from 'uiSrc/pages/home/constants'
import { getFormErrors, getSubmitButtonContent } from 'uiSrc/pages/home/utils'
import { DbConnectionInfo, ISubmitButton, IPasswordType } from 'uiSrc/pages/home/interfaces'
import { DbConnectionInfo, ISubmitButton } from 'uiSrc/pages/home/interfaces'
import {
MessageSentinel,
TlsDetails,
Expand Down Expand Up @@ -171,7 +171,6 @@ const SentinelConnectionForm = (props: Props) => {
formik={formik}
flexItemClassName={flexItemClassName}
flexGroupClassName={flexGroupClassName}
passwordType={IPasswordType.dual}
showFields={{ host: true, port: true, alias: false, timeout: false }}
onHostNamePaste={onHostNamePaste}
/>
Expand Down
7 changes: 1 addition & 6 deletions redisinsight/ui/src/pages/home/interfaces/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface DbConnectionInfo extends Instance {
showCompressor?: boolean
sni?: boolean
sentinelMasterUsername?: string
sentinelMasterPassword?: string
sentinelMasterPassword?: string | true
sentinelMasterName?: string
ssh?: boolean
sshPassType?: string
Expand All @@ -40,8 +40,3 @@ export interface ISubmitButton {
text?: string
submitIsDisabled?: boolean
}

export enum IPasswordType {
Password = 'password',
Dual = 'dual',
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ test
// Validate Databases section
await t
.click(myRedisDatabasePage.AddRedisDatabase.cloneSentinelDatabaseNavigation)
.expect(myRedisDatabasePage.AddRedisDatabase.masterGroupPassword.getAttribute('value')).eql(ossSentinelConfig.masters[1].password, 'Invalid sentinel database password');
.expect(myRedisDatabasePage.AddRedisDatabase.masterGroupPassword.getAttribute('value')).eql(hiddenPassword, 'Invalid sentinel database password');
// Validate Sentinel section
await t
.click(myRedisDatabasePage.AddRedisDatabase.cloneSentinelNavigation)
Expand Down
2 changes: 0 additions & 2 deletions tests/e2e/tests/web/regression/database/github.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// import {ClientFunction} from 'testcafe';
import { rte } from '../../../../helpers/constants';
import { DatabaseHelper } from '../../../../helpers/database';
import { MyRedisDatabasePage } from '../../../../pageObjects';
Expand All @@ -9,7 +8,6 @@ import { Common } from '../../../../helpers/common';
const myRedisDatabasePage = new MyRedisDatabasePage();
const databaseHelper = new DatabaseHelper();
const databaseAPIRequests = new DatabaseAPIRequests();
// const getPageUrl = ClientFunction(() => window.location.href);

fixture `Github functionality`
.meta({ type: 'regression', rte: rte.standalone })
Expand Down