Skip to content

Commit 8f108ad

Browse files
Merge pull request #2216 from RedisInsight/be/feature/RI-4454_websocket_security
#RI-4567 - add security for socket
2 parents 0ba7e43 + efb3547 commit 8f108ad

File tree

8 files changed

+55
-3
lines changed

8 files changed

+55
-3
lines changed

redisinsight/api/src/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { GlobalExceptionFilter } from 'src/exceptions/global-exception.filter';
88
import { get } from 'src/utils';
99
import { migrateHomeFolder } from 'src/init-helper';
1010
import { LogFileProvider } from 'src/modules/profiler/providers/log-file.provider';
11+
import { WindowsAuthAdapter } from 'src/modules/auth/window-auth/adapters/window-auth.adapter';
1112
import { AppModule } from './app.module';
1213
import SWAGGER_CONFIG from '../config/swagger';
1314
import LOGGER_CONFIG from '../config/logger';
@@ -49,6 +50,8 @@ export default async function bootstrap(): Promise<IApp> {
4950
app,
5051
SwaggerModule.createDocument(app, SWAGGER_CONFIG),
5152
);
53+
} else {
54+
app.useWebSocketAdapter(new WindowsAuthAdapter(app));
5255
}
5356

5457
const logFileProvider = app.get(LogFileProvider);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { INestApplication, Logger } from '@nestjs/common';
2+
import { IoAdapter } from '@nestjs/platform-socket.io';
3+
import { BaseWsInstance, MessageMappingProperties } from '@nestjs/websockets';
4+
import { get } from 'lodash';
5+
import { Observable } from 'rxjs';
6+
import { Socket } from 'socket.io';
7+
import { API_HEADER_WINDOW_ID } from 'src/common/constants';
8+
import ERROR_MESSAGES from 'src/constants/error-messages';
9+
import { WindowAuthService } from '../window-auth.service';
10+
11+
export class WindowsAuthAdapter extends IoAdapter {
12+
private windowAuthService: WindowAuthService;
13+
private logger = new Logger('WindowsAuthAdapter');
14+
15+
constructor(private app: INestApplication) {
16+
super(app);
17+
this.windowAuthService = this.app.get(WindowAuthService);
18+
}
19+
20+
async bindMessageHandlers(
21+
socket: Socket,
22+
handlers: MessageMappingProperties[],
23+
transform: (data: any) => Observable<any>,
24+
) {
25+
const windowId = (get(socket, `handshake.headers.${API_HEADER_WINDOW_ID}`) as string) || '';
26+
const isAuthorized = await this.windowAuthService?.isAuthorized(windowId);
27+
28+
if (!isAuthorized) {
29+
this.logger.error(ERROR_MESSAGES.UNDEFINED_WINDOW_ID);
30+
return;
31+
}
32+
33+
return super.bindMessageHandlers(socket, handlers, transform);
34+
}
35+
}

redisinsight/ui/src/components/bulk-actions-config/BulkActionsConfig.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
1919
import { isProcessingBulkAction } from 'uiSrc/pages/browser/components/bulk-actions/utils'
2020
import { BrowserStorageItem, BulkActionsServerEvent, BulkActionsStatus, BulkActionsType, SocketEvent } from 'uiSrc/constants'
2121
import { addErrorNotification } from 'uiSrc/slices/app/notifications'
22+
import { CustomHeaders } from 'uiSrc/constants/api'
2223

2324
interface IProps {
2425
retryDelay?: number
@@ -43,6 +44,7 @@ const BulkActionsConfig = ({ retryDelay = 5000 } : IProps) => {
4344
socketRef.current = io(`${getBaseApiUrl()}/bulk-actions`, {
4445
forceNew: true,
4546
query: { instanceId },
47+
extraHeaders: { [CustomHeaders.WindowId]: window.windowId || '' },
4648
rejectUnauthorized: false,
4749
})
4850

redisinsight/ui/src/components/global-subscriptions/CommonAppSubscription/CommonAppSubscription.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
1212
import { setTotalUnread } from 'uiSrc/slices/recommendations/recommendations'
1313
import { RecommendationsSocketEvents } from 'uiSrc/constants/recommendations'
1414
import { getFeatureFlagsSuccess } from 'uiSrc/slices/app/features'
15+
import { CustomHeaders } from 'uiSrc/constants/api'
1516

1617
const CommonAppSubscription = () => {
1718
const { id: instanceId } = useSelector(connectedInstanceSelector)
@@ -28,7 +29,8 @@ const CommonAppSubscription = () => {
2829
socketRef.current = io(`${getBaseApiUrl()}`, {
2930
forceNew: false,
3031
rejectUnauthorized: false,
31-
reconnection: true
32+
reconnection: true,
33+
extraHeaders: { [CustomHeaders.WindowId]: window.windowId || '' },
3234
})
3335

3436
socketRef.current.on(SocketEvent.Connect, () => {

redisinsight/ui/src/components/monitor-config/MonitorConfig.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { getBaseApiUrl } from 'uiSrc/utils'
2020
import { MonitorErrorMessages, MonitorEvent, SocketErrors, SocketEvent } from 'uiSrc/constants'
2121
import { IMonitorDataPayload } from 'uiSrc/slices/interfaces'
2222
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
23+
import { CustomHeaders } from 'uiSrc/constants/api'
2324
import { IMonitorData } from 'apiSrc/modules/profiler/interfaces/monitor-data.interface'
2425

2526
import ApiStatusCode from '../../constants/apiStatusCode'
@@ -59,6 +60,7 @@ const MonitorConfig = ({ retryDelay = 15000 } : IProps) => {
5960
const newSocket = io(`${getBaseApiUrl()}/monitor`, {
6061
forceNew: true,
6162
query: { instanceId },
63+
extraHeaders: { [CustomHeaders.WindowId]: window.windowId || '' },
6264
rejectUnauthorized: false,
6365
})
6466
dispatch(setSocket(newSocket))

redisinsight/ui/src/components/pub-sub-config/PubSubConfig.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'
33
import { io, Socket } from 'socket.io-client'
44

55
import { SocketEvent } from 'uiSrc/constants'
6+
import { CustomHeaders } from 'uiSrc/constants/api'
67
import { PubSubEvent } from 'uiSrc/constants/pubSub'
78
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
89
import { PubSubSubscription } from 'uiSrc/slices/interfaces/pubsub'
@@ -37,6 +38,7 @@ const PubSubConfig = ({ retryDelay = 5000 } : IProps) => {
3738
socketRef.current = io(`${getBaseApiUrl()}/pub-sub`, {
3839
forceNew: true,
3940
query: { instanceId },
41+
extraHeaders: { [CustomHeaders.WindowId]: window.windowId || '' },
4042
rejectUnauthorized: false,
4143
})
4244

redisinsight/ui/src/constants/api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ enum ApiEndpoints {
114114
FEATURES = 'features',
115115
}
116116

117+
export enum CustomHeaders {
118+
DbIndex = 'ri-db-index',
119+
WindowId = 'x-window-id',
120+
}
121+
117122
export const DEFAULT_SEARCH_MATCH = '*'
118123

119124
const SCAN_COUNT_DEFAULT_ENV = process.env.SCAN_COUNT_DEFAULT || '500'

redisinsight/ui/src/services/apiService.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import axios, { AxiosRequestConfig } from 'axios'
22
import { isNumber } from 'lodash'
33
import { sessionStorageService } from 'uiSrc/services'
44
import { BrowserStorageItem } from 'uiSrc/constants'
5+
import { CustomHeaders } from 'uiSrc/constants/api'
56

67
const { apiPort } = window.app.config
78
const baseApiUrl = process.env.BASE_API_URL
@@ -26,12 +27,12 @@ export const requestInterceptor = (config: AxiosRequestConfig) => {
2627
const dbIndex = sessionStorageService.get(`${BrowserStorageItem.dbIndex}${instanceId}`)
2728

2829
if (isNumber(dbIndex)) {
29-
config.headers['ri-db-index'] = dbIndex
30+
config.headers[CustomHeaders.DbIndex] = dbIndex
3031
}
3132
}
3233

3334
if (window.windowId) {
34-
config.headers['x-window-id'] = window.windowId
35+
config.headers[CustomHeaders.WindowId] = window.windowId
3536
}
3637
}
3738

0 commit comments

Comments
 (0)