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
1 change: 1 addition & 0 deletions redisinsight/api/config/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export default {
appVersion: process.env.APP_VERSION || '2.0.0',
requestTimeout: parseInt(process.env.REQUEST_TIMEOUT, 10) || 10000,
excludeRoutes: [],
excludeAuthRoutes: [],
},
sockets: {
cors: process.env.SOCKETS_CORS ? process.env.SOCKETS_CORS === 'true' : false,
Expand Down
34 changes: 34 additions & 0 deletions redisinsight/api/src/__mocks__/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { ClientContext, ClientMetadata, Session } from 'src/common/models';
import { mockDatabase } from 'src/__mocks__/databases';
import { v4 as uuidv4 } from 'uuid';

export type MockType<T> = {
[P in keyof T]: jest.Mock<any>;
};
Expand Down Expand Up @@ -57,3 +61,33 @@ export const mockRepository = jest.fn(() => ({
remove: jest.fn(),
createQueryBuilder: mockCreateQueryBuilder,
}));

export const mockSession: Session = {
userId: uuidv4(),
sessionId: uuidv4(),
};

export const mockCliClientMetadata: ClientMetadata = {
session: mockSession,
databaseId: mockDatabase.id,
context: ClientContext.CLI,
uniqueId: uuidv4(),
};

export const mockWorkbenchClientMetadata: ClientMetadata = {
session: mockSession,
databaseId: mockDatabase.id,
context: ClientContext.Workbench,
};

export const mockBrowserClientMetadata: ClientMetadata = {
session: mockSession,
databaseId: mockDatabase.id,
context: ClientContext.Browser,
};

export const mockCommonClientMetadata: ClientMetadata = {
session: mockSession,
databaseId: mockDatabase.id,
context: ClientContext.Common,
};
7 changes: 0 additions & 7 deletions redisinsight/api/src/__mocks__/databases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { mockIORedisClient } from 'src/__mocks__/redis';
import { mockSentinelMasterDto } from 'src/__mocks__/redis-sentinel';
import { pick } from 'lodash';
import { RedisDatabaseInfoResponse } from 'src/modules/database/dto/redis-info.dto';
import { ClientMetadata } from 'src/modules/redis/models/client-metadata';
import { AppTool } from 'src/models';
import { DatabaseOverview } from 'src/modules/database/models/database-overview';

export const mockDatabaseId = 'a77b23c1-7816-4ea4-b61f-d37795a0f805-db-id';
Expand Down Expand Up @@ -115,11 +113,6 @@ export const mockClusterDatabaseWithTlsAuthEntity = Object.assign(new DatabaseEn
nodes: JSON.stringify(mockClusterNodes),
});

export const mockClientMetadata: ClientMetadata = {
databaseId: mockDatabase.id,
namespace: AppTool.Common,
};

export const mockDatabaseOverview: DatabaseOverview = {
version: '6.2.4',
usedMemory: 1,
Expand Down
6 changes: 6 additions & 0 deletions redisinsight/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ServerModule } from 'src/modules/server/server.module';
import { LocalDatabaseModule } from 'src/local-database.module';
import { CoreModule } from 'src/core.module';
import { AutodiscoveryModule } from 'src/modules/autodiscovery/autodiscovery.module';
import { DummyAuthMiddleware } from 'src/common/middlewares/dummy-auth.middleware';
import { BrowserModule } from './modules/browser/browser.module';
import { RedisEnterpriseModule } from './modules/redis-enterprise/redis-enterprise.module';
import { RedisSentinelModule } from './modules/redis-sentinel/redis-sentinel.module';
Expand Down Expand Up @@ -97,6 +98,11 @@ export class AppModule implements OnModuleInit, NestModule {
}

configure(consumer: MiddlewareConsumer) {
consumer
.apply(DummyAuthMiddleware)
.exclude(...SERVER_CONFIG.excludeAuthRoutes)
.forRoutes('*');

consumer
.apply(ExcludeRouteMiddleware)
.forRoutes(
Expand Down
2 changes: 2 additions & 0 deletions redisinsight/api/src/common/constants/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const API_PARAM_DATABASE_ID = 'dbInstance';
export const API_PARAM_CLI_CLIENT_ID = 'uuid';
1 change: 1 addition & 0 deletions redisinsight/api/src/common/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './redis-string';
export * from './api';
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { BadRequestException, createParamDecorator, ExecutionContext } from '@nestjs/common';
import { plainToClass } from 'class-transformer';
import { ClientContext, ClientMetadata } from 'src/common/models';
import { sessionFromRequestFactory } from 'src/common/decorators';
import { Validator } from 'class-validator';
import { API_PARAM_DATABASE_ID } from 'src/common/constants';

const validator = new Validator();

export interface IClientMetadataParamOptions {
databaseIdParam?: string,
uniqueIdParam?: string,
context?: ClientContext,
}

export const clientMetadataParamFactory = (
options: IClientMetadataParamOptions,
ctx: ExecutionContext,
): ClientMetadata => {
const opts: IClientMetadataParamOptions = {
context: ClientContext.Common,
databaseIdParam: API_PARAM_DATABASE_ID,
...options,
};

const req = ctx.switchToHttp().getRequest();

let databaseId;
if (opts?.databaseIdParam) {
databaseId = req.params?.[opts.databaseIdParam];
}

let uniqueId;
if (opts?.uniqueIdParam) {
uniqueId = req.params?.[opts.uniqueIdParam];
}

const clientMetadata = plainToClass(ClientMetadata, {
session: sessionFromRequestFactory(undefined, ctx),
databaseId,
uniqueId,
context: opts?.context || ClientContext.Common,
});

const errors = validator.validateSync(clientMetadata, {
whitelist: false, // we need this to allow additional fields if needed for flexibility
});

if (errors?.length) {
throw new BadRequestException(Object.values(errors[0].constraints) || 'Bad request');
}

return clientMetadata;
};

export const ClientMetadataParam = createParamDecorator(clientMetadataParamFactory);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './client-metadata.decorator';
2 changes: 2 additions & 0 deletions redisinsight/api/src/common/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export * from './redis-string';
export * from './zset-score';
export * from './default';
export * from './data-as-json-string.decorator';
export * from './session';
export * from './client-metadata';
1 change: 1 addition & 0 deletions redisinsight/api/src/common/decorators/session/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './session.decorator';
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BadRequestException, createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Validator } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { Session } from 'src/common/models';

const validator = new Validator();

export const sessionFromRequestFactory = (data: unknown, ctx: ExecutionContext): Session => {
const request = ctx.switchToHttp().getRequest();

const session = plainToClass(Session, request.session);

const errors = validator.validateSync(session, {
whitelist: false, // we need this to allow additional fields if needed for flexibility
});

if (errors?.length) {
throw new BadRequestException(Object.values(errors[0].constraints) || 'Bad request');
}

return session;
};

export const SessionFromRequest = createParamDecorator(sessionFromRequestFactory);
18 changes: 18 additions & 0 deletions redisinsight/api/src/common/middlewares/dummy-auth.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
Injectable,
NestMiddleware,
} from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
import { ISession } from 'src/common/models/session';

@Injectable()
export class DummyAuthMiddleware implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction): Promise<any> {
req['session'] = <ISession>Object.freeze({
userId: '1',
sessionId: '1',
});

next();
}
}
30 changes: 30 additions & 0 deletions redisinsight/api/src/common/models/client-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Session } from 'src/common/models/session';
import { Type } from 'class-transformer';
import {
IsEnum, IsNotEmpty, IsOptional, IsString,
} from 'class-validator';

export enum ClientContext {
Common = 'Common',
Browser = 'Browser',
CLI = 'CLI',
Workbench = 'Workbench',
}

export class ClientMetadata {
@IsNotEmpty()
@Type(() => Session)
session: Session;

@IsNotEmpty()
@IsString()
databaseId: string;

@IsNotEmpty()
@IsEnum(ClientContext)
context: ClientContext;

@IsOptional()
@IsString()
uniqueId?: string;
}
2 changes: 2 additions & 0 deletions redisinsight/api/src/common/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './common';
export * from './endpoint';
export * from './session';
export * from './client-metadata';
21 changes: 21 additions & 0 deletions redisinsight/api/src/common/models/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';

export interface ISession {
userId: string;
sessionId: string;
uniqueId?: string;
}

export class Session implements ISession {
@IsNotEmpty()
@IsString()
userId: string;

@IsNotEmpty()
@IsString()
sessionId: string;

@IsOptional()
@IsString()
uniqueId?: string;
}
6 changes: 3 additions & 3 deletions redisinsight/api/src/models/redis-consumer.interface.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { IFindRedisClientInstanceByOptions } from 'src/modules/redis/redis.service';
import { ReplyError } from 'src/models/redis-client';
import { Cluster, Redis } from 'ioredis';
import { ClientMetadata } from 'src/common/models';

export interface IRedisConsumer {
execCommand(
clientOptions: IFindRedisClientInstanceByOptions,
clientMetadata: ClientMetadata,
toolCommand: any,
args: Array<string | number | Buffer>,
): any;

execPipeline(
clientOptions: IFindRedisClientInstanceByOptions,
clientMetadata: ClientMetadata,
toolCommands: Array<
[toolCommand: any, ...args: Array<string | number | Buffer>]
>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import config from 'src/utils/config';
import { SettingsService } from 'src/modules/settings/settings.service';
import { Database } from 'src/modules/database/models/database';
import { DatabaseService } from 'src/modules/database/database.service';
import { ClientContext } from 'src/common/models';

const SERVER_CONFIG = config.get('server');

Expand Down Expand Up @@ -74,7 +75,7 @@ export class AutodiscoveryService implements OnModuleInit {
try {
const client = await this.redisService.createStandaloneClient(
endpoint as Database,
AppTool.Common,
ClientContext.Common,
false,
'redisinsight-auto-discovery',
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ import {
Controller,
Delete,
HttpCode,
Param,
Post,
Put,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import {
ApiBody, ApiOkResponse, ApiOperation, ApiTags,
} from '@nestjs/swagger';
import { ApiRedisParams } from 'src/decorators/api-redis-params.decorator';
import { BaseController } from 'src/modules/browser/controllers/base.controller';
import { BrowserClientMetadata } from 'src/modules/browser/decorators/browser-client-metadata.decorator';
import { ApiQueryRedisStringEncoding } from 'src/common/decorators';
import { ClientMetadata } from 'src/common/models';
import {
AddFieldsToHashDto,
CreateHashWithExpireDto,
Expand All @@ -38,15 +37,10 @@ export class HashController extends BaseController {
@ApiBody({ type: CreateHashWithExpireDto })
@ApiQueryRedisStringEncoding()
async createHash(
@Param('dbInstance') dbInstance: string,
@BrowserClientMetadata() clientMetadata: ClientMetadata,
@Body() dto: CreateHashWithExpireDto,
): Promise<void> {
return await this.hashBusinessService.createHash(
{
instanceId: dbInstance,
},
dto,
);
return await this.hashBusinessService.createHash(clientMetadata, dto);
}

// The key name can be very large, so it is better to send it in the request body
Expand All @@ -63,15 +57,10 @@ export class HashController extends BaseController {
})
@ApiQueryRedisStringEncoding()
async getMembers(
@Param('dbInstance') dbInstance: string,
@BrowserClientMetadata() clientMetadata: ClientMetadata,
@Body() dto: GetHashFieldsDto,
): Promise<GetHashFieldsResponse> {
return await this.hashBusinessService.getFields(
{
instanceId: dbInstance,
},
dto,
);
return await this.hashBusinessService.getFields(clientMetadata, dto);
}

@Put('')
Expand All @@ -82,15 +71,10 @@ export class HashController extends BaseController {
@ApiBody({ type: AddFieldsToHashDto })
@ApiQueryRedisStringEncoding()
async addMember(
@Param('dbInstance') dbInstance: string,
@BrowserClientMetadata() clientMetadata: ClientMetadata,
@Body() dto: AddFieldsToHashDto,
): Promise<void> {
return await this.hashBusinessService.addFields(
{
instanceId: dbInstance,
},
dto,
);
return await this.hashBusinessService.addFields(clientMetadata, dto);
}

@Delete('/fields')
Expand All @@ -101,14 +85,9 @@ export class HashController extends BaseController {
@ApiBody({ type: DeleteFieldsFromHashDto })
@ApiQueryRedisStringEncoding()
async deleteFields(
@Param('dbInstance') dbInstance: string,
@BrowserClientMetadata() clientMetadata: ClientMetadata,
@Body() dto: DeleteFieldsFromHashDto,
): Promise<DeleteFieldsFromHashResponse> {
return await this.hashBusinessService.deleteFields(
{
instanceId: dbInstance,
},
dto,
);
return await this.hashBusinessService.deleteFields(clientMetadata, dto);
}
}
Loading