Skip to content
Closed
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 .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = {
root: true,
env: {
node: true,
browser: true,
},
extends: ['airbnb-typescript'],
plugins: ['@typescript-eslint'],
Expand Down
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
{
"name": "redisinsight",
"version": "1.0.0",
"productName": "Redis Insight",
"description": "Redis Insight",
"license": "SSPL",
"private": true,
"scripts": {
"dev:ui": "yarn --cwd redisinsight/ui dev",
"dev:ui": "cross-env yarn --cwd redisinsight/ui dev",
"dev:api": "cross-env yarn --cwd redisinsight/api start:dev",
"dev:electron:ui": "cross-env RI_APP_TYPE=electron NODE_ENV=development yarn --cwd redisinsight/ui dev",
"dev:electron:api": "cross-env RI_APP_TYPE=electron NODE_ENV=development yarn --cwd redisinsight/api start:dev",
"dev:electron": "cross-env RI_APP_TYPE=electron NODE_ENV=development yarn --cwd redisinsight/desktop dev",
"build:ui": "cross-env NODE_ENV=production RI_APP_TYPE=web yarn --cwd redisinsight/ui build",
"build:renderer": "cross-env NODE_ENV=production RI_APP_TYPE=electron yarn --cwd redisinsight/ui build --emptyOutDir && copyfiles ./redisinsight/desktop/splash.html ./redisinsight/dist/renderer -f",
"stats:ui": "yarn --cwd redisinsight/ui stats",
Expand All @@ -23,6 +28,7 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint:ui": "eslint ./redisinsight/ui --ext .js,.jsx,.ts,.tsx",
"lint:api": "yarn --cwd redisinsight/api lint",
"lint:desktop": "eslint ./redisinsight/desktop",
"lint:e2e": "yarn --cwd tests/e2e lint",
"package": "yarn package:dev",
"package:prod": "ts-node ./scripts/prebuild.js dist && yarn build:prod && electron-builder build -p never",
Expand Down Expand Up @@ -244,6 +250,7 @@
"monaco-editor": "^0.48.0",
"monaco-yaml": "^5.1.1",
"msgpackr": "^1.10.1",
"node-abi": "^3.71.0",
"pako": "^2.1.0",
"php-serialize": "^4.0.2",
"pickleparser": "^0.2.1",
Expand Down
1 change: 0 additions & 1 deletion redisinsight/api/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const SERVER_CONFIG = config.get('server') as Config['server'];
export class AuthModule {
static register() {
const imports = [];

if (SERVER_CONFIG.buildType === BuildType.Electron) {
imports.push(WindowAuthModule);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import { WindowUnauthorizedException } from '../constants/exceptions';
@Injectable()
export class WindowAuthMiddleware implements NestMiddleware {
private logger = new Logger('WindowAuthMiddleware');

constructor(private windowAuthService: WindowAuthService) {}

async use(req: Request, res: Response, next: NextFunction): Promise<any> {
const { windowId } = WindowAuthMiddleware.getWindowIdFromReq(req);

if (!windowId && req.path.startsWith('/static/')) {
return next();
}

const isAuthorized = await this.windowAuthService.isAuthorized(windowId);

if (!isAuthorized) {
Expand All @@ -23,7 +27,8 @@ export class WindowAuthMiddleware implements NestMiddleware {
}

private static getWindowIdFromReq(req: Request) {
return { windowId: `${req?.headers?.[API_HEADER_WINDOW_ID]}` };
const windowId = req?.headers?.[API_HEADER_WINDOW_ID];
return { windowId: windowId ? `${windowId}` : undefined };
}

private throwError(req: Request, message: string) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Injectable } from '@nestjs/common';
import { createConnection } from 'net';
import { AbstractWindowAuthStrategy } from './abstract.window.auth.strategy';

@Injectable()
export class ElectronWindowAuthStrategy extends AbstractWindowAuthStrategy {
private authPort = process.env.TCP_LOCAL_AUTH_PORT ? parseInt(process.env.TCP_LOCAL_AUTH_PORT) : 5541;

async isAuthorized(id: string): Promise<boolean> {
return new Promise((resolve) => {
const client = createConnection(this.authPort, 'localhost', () => {
client.write(id);
});

client.on('data', (data) => {
const isValid = data.toString() === '1';
client.end();
resolve(isValid);
});

client.on('error', (err) => {
resolve(false);
});

// Don't end the connection until we get data or timeout
setTimeout(() => {
client.destroy();
resolve(false);
}, 5000);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import config, { Config } from 'src/utils/config';
import config, { Config } from '../../../utils/config';
import { WindowAuthService } from './window-auth.service';
import { WindowAuthMiddleware } from './middleware/window.auth.middleware';
import { ElectronWindowAuthStrategy } from './strategies/electron.window.auth.strategy';

const SERVER_CONFIG = config.get('server') as Config['server'];

@Module({
providers: [WindowAuthService],
providers: [
WindowAuthService,
ElectronWindowAuthStrategy,
],
exports: [WindowAuthService],
})
export class WindowAuthModule implements NestModule {
constructor(
private readonly windowAuthService: WindowAuthService,
private readonly electronStrategy: ElectronWindowAuthStrategy,
) {
if (process.env.RI_APP_TYPE === 'electron' && process.env.NODE_ENV === 'development') {
this.windowAuthService.setStrategy(this.electronStrategy);
}
}

configure(consumer: MiddlewareConsumer) {
consumer
.apply(WindowAuthMiddleware)
.exclude(...SERVER_CONFIG.excludeAuthRoutes)
.exclude(
...SERVER_CONFIG.excludeAuthRoutes,
'/static/(.*)', // Exclude all static content
)
.forRoutes('*');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class ListService {
}

this.logger.log(`Succeed to insert element at the ${destination} of the list data type.`);
return plainToClass(PushListElementsResponse, { keyName, total });
return plainToClass(PushListElementsResponse, { keyName, total });
} catch (error) {
this.logger.error('Failed to inserts element to the list data type.', error);
if (error.message.includes(RedisErrorCodes.WrongType)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Injectable, Logger } from '@nestjs/common';
import { createServer, Server } from 'net';
import { CloudAuthStrategy } from './cloud-auth.strategy';
import { CloudAuthService } from '../cloud-auth.service';

@Injectable()
export class TcpCloudAuthStrategy extends CloudAuthStrategy {
private authPort = process.env.TCP_LOCAL_CLOUD_AUTH_PORT ? parseInt(process.env.TCP_LOCAL_CLOUD_AUTH_PORT) : 5542;
private server: Server;
private readonly logger = new Logger('TcpCloudAuthStrategy');

constructor(private readonly cloudAuthService: CloudAuthService) {
super();

this.initServer();
}

private initServer() {
this.server = createServer((socket) => {
socket.setEncoding('utf8');

socket.on('data', async (data) => {
try {
this.logger.debug(`Received raw data: ${data.toString()}`);
const { action, options } = JSON.parse(data.toString());
this.logger.debug('Parsed request:', { action, options });

if (action === 'getAuthUrl') {
try {
const url = await this.cloudAuthService.getAuthorizationUrl(
options.sessionMetadata,
options.authOptions
);
this.logger.debug('Generated URL:', url);
socket.write(JSON.stringify({ success: true, url }));
} catch (err) {
this.logger.error('Error getting authorization URL:', err);
socket.write(JSON.stringify({
success: false,
error: err.message,
details: err.stack,
context: { action, options } // Add the context to help debug
}));
}
} else if (action === 'handleCallback') {
try {
this.logger.debug('Handling callback with query:', options.query);
const result = await this.cloudAuthService.handleCallback(options.query);
socket.write(JSON.stringify({ success: true, result }));
} catch (err) {
this.logger.error('Error handling callback:', err);
socket.write(JSON.stringify({
success: false,
error: err.message,
details: err.stack
}));
}
}
} catch (err) {
this.logger.error('Error processing request:', err);
socket.write(JSON.stringify({
success: false,
error: err.message,
details: err.stack
}));
}
socket.end();
});

socket.on('end', () => {
this.logger.debug('Client connection ended');
});

socket.on('error', (err) => {
this.logger.error('Socket error:', err);
});
});

this.server.listen(this.authPort, () => {
this.logger.log(`TCP server listening on port ${this.authPort}`);
});

this.server.on('error', (err) => {
this.logger.error('Server error:', err);
});
}
}
44 changes: 43 additions & 1 deletion redisinsight/api/src/modules/cloud/cloud.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,50 @@ import { CloudUserModule } from 'src/modules/cloud/user/cloud-user.module';
import { CloudTaskModule } from 'src/modules/cloud/task/cloud-task.module';
import { CloudJobModule } from 'src/modules/cloud/job/cloud-job.module';
import { CloudCapiKeyModule } from 'src/modules/cloud/capi-key/cloud-capi-key.module';
import { CloudAuthService } from './auth/cloud-auth.service';
import { TcpCloudAuthStrategy } from './auth/auth-strategy/tcp-cloud.auth.strategy';
import { CloudAuthStrategy } from './auth/auth-strategy/cloud-auth.strategy';
import { CloudSessionService } from './session/cloud-session.service';
import { CloudAuthAnalytics } from './auth/cloud-auth.analytics';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { GoogleIdpCloudAuthStrategy } from './auth/auth-strategy/google-idp.cloud.auth-strategy';
import { GithubIdpCloudAuthStrategy } from './auth/auth-strategy/github-idp.cloud.auth-strategy';
import { SsoIdpCloudAuthStrategy } from './auth/auth-strategy/sso-idp.cloud.auth-strategy';

@Module({})
@Module({
imports: [
EventEmitterModule.forRoot(),
CloudAutodiscoveryModule,
CloudAuthModule,
CloudUserModule,
CloudTaskModule,
CloudJobModule,
CloudCapiKeyModule,
],
providers: [
// Services
CloudAuthService,
CloudSessionService,
CloudAuthAnalytics,

// Auth Strategies
GoogleIdpCloudAuthStrategy,
GithubIdpCloudAuthStrategy,
SsoIdpCloudAuthStrategy,
{
provide: CloudAuthStrategy,
useClass: TcpCloudAuthStrategy
}
],
exports: [
CloudAuthService,
CloudAuthStrategy,
// Export IDP strategies if needed by other modules
GoogleIdpCloudAuthStrategy,
GithubIdpCloudAuthStrategy,
SsoIdpCloudAuthStrategy,
]
})
export class CloudModule {
static register() {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export class DatabaseOverviewProvider {
return [undefined, undefined];
}

const totalKeysPerDb = {};
const totalKeysPerDb: Record<string, number> = {};

masterNodes.forEach((node) => {
map(
Expand Down
9 changes: 8 additions & 1 deletion redisinsight/api/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ switch (process.env.RI_BUILD_TYPE) {
break;
}

merge(config, envConfig, buildTypeConfig);
const determineAppBuildType = () => {
if (process.env.RI_APP_TYPE?.toLowerCase() === 'electron') {
return { server:{ buildType: 'ELECTRON' } };
}
return {};
};

merge(config, envConfig, buildTypeConfig, determineAppBuildType());

export type Config = typeof config;
export type KeyOfConfig = keyof typeof config;
Expand Down
Loading