Skip to content

Commit 3cceea2

Browse files
add auth retry for data chatbot
1 parent b874200 commit 3cceea2

File tree

6 files changed

+63
-22
lines changed

6 files changed

+63
-22
lines changed

redisinsight/api/src/modules/ai/query/ai-query.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export class AiQueryService {
121121
accountId: auth.accountId,
122122
});
123123

124-
socket = await this.aiQueryProvider.getSocket(auth);
124+
socket = await this.aiQueryProvider.getSocket(sessionMetadata, auth);
125125

126126
socket.on(AiQueryWsEvents.REPLY_CHUNK, (chunk) => {
127127
answer.content += chunk;

redisinsight/api/src/modules/ai/query/exceptions/ai-query.error.handler.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AxiosError } from 'axios';
2+
import { get } from 'lodash';
23
import { HttpException } from '@nestjs/common';
34
import {
45
AiQueryUnauthorizedException,
@@ -13,11 +14,12 @@ export const wrapAiQueryError = (error: AxiosError, message?: string): HttpExcep
1314
return error;
1415
}
1516

16-
const { response } = error;
17+
// TransportError or Axios error
18+
const response = get(error, ['description', 'target', '_req', 'res'], error.response);
1719

1820
if (response) {
1921
const errorOptions = { cause: new Error(response?.data as string) };
20-
switch (response?.status) {
22+
switch (response?.status || response?.statusCode) {
2123
case 401:
2224
return new AiQueryUnauthorizedException(message, errorOptions);
2325
case 403:

redisinsight/api/src/modules/ai/query/providers/ai-query.provider.ts

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,47 @@ import config, { Config } from 'src/utils/config';
33
import { Injectable, Logger } from '@nestjs/common';
44
import { AiQueryAuthData } from 'src/modules/ai/query/models/ai-query.auth-data';
55
import { AiQueryWsEvents } from 'src/modules/ai/query/models';
6+
import { AiQueryAuthProvider } from 'src/modules/ai/query/providers/auth/ai-query-auth.provider';
7+
import { SessionMetadata } from 'src/common/models';
8+
import { wrapAiQueryError } from 'src/modules/ai/query/exceptions';
69

710
const aiConfig = config.get('ai') as Config['ai'];
811

912
@Injectable()
1013
export class AiQueryProvider {
1114
private readonly logger = new Logger('AiQueryProvider');
1215

13-
async getSocket(auth: AiQueryAuthData): Promise<Socket> {
14-
return new Promise((resolve, reject) => {
15-
const socket = io(aiConfig.querySocketUrl, {
16-
path: aiConfig.querySocketPath,
17-
reconnection: false,
18-
transports: ['websocket'],
19-
extraHeaders: {
20-
'X-Csrf-Token': auth.csrf,
21-
Cookie: `JSESSIONID=${auth.sessionId}`,
22-
},
23-
});
16+
constructor(
17+
private readonly aiQueryAuthProvider: AiQueryAuthProvider,
18+
) {}
2419

25-
socket.on(AiQueryWsEvents.CONNECT_ERROR, (e) => {
26-
this.logger.error('Unable to establish AI socket connection', e);
27-
reject(e);
28-
});
20+
async getSocket(sessionMetadata: SessionMetadata, auth: AiQueryAuthData): Promise<Socket> {
21+
return this.aiQueryAuthProvider.callWithAuthRetry(sessionMetadata, async () => {
22+
try {
23+
return await new Promise((resolve, reject) => {
24+
const socket = io(aiConfig.querySocketUrl, {
25+
path: aiConfig.querySocketPath,
26+
reconnection: false,
27+
transports: ['websocket'],
28+
extraHeaders: {
29+
'X-Csrf-Token': auth.csrf,
30+
Cookie: `JSESSIONID=${auth.sessionId}`,
31+
},
32+
});
2933

30-
socket.on(AiQueryWsEvents.CONNECT, async () => {
31-
this.logger.debug('AI socket connection established');
32-
resolve(socket);
33-
});
34+
socket.on(AiQueryWsEvents.CONNECT_ERROR, (e) => {
35+
this.logger.error('Unable to establish AI socket connection', e);
36+
reject(e);
37+
});
38+
39+
socket.on(AiQueryWsEvents.CONNECT, async () => {
40+
this.logger.debug('AI socket connection established');
41+
resolve(socket);
42+
});
43+
});
44+
} catch (e) {
45+
throw wrapAiQueryError(e, 'Unable to establish connection');
46+
}
3447
});
3548
}
3649
}

redisinsight/api/src/modules/ai/query/providers/auth/ai-query-auth.provider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ import { AiQueryAuthData } from 'src/modules/ai/query/models/ai-query.auth-data'
33

44
export abstract class AiQueryAuthProvider {
55
abstract getAuthData(sessionMetadata: SessionMetadata): Promise<AiQueryAuthData>;
6+
abstract callWithAuthRetry(sessionId: SessionMetadata, fn: () => Promise<any>, retries?: number): Promise<any>;
67
}

redisinsight/api/src/modules/ai/query/providers/auth/local.ai-query-auth.provider.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { SessionMetadata } from 'src/common/models';
33
import { AiQueryAuthData } from 'src/modules/ai/query/models/ai-query.auth-data';
44
import { AiQueryAuthProvider } from 'src/modules/ai/query/providers/auth/ai-query-auth.provider';
55
import { CloudUserApiService } from 'src/modules/cloud/user/cloud-user.api.service';
6+
import { CloudApiUnauthorizedException } from 'src/modules/cloud/common/exceptions';
7+
import { AiQueryUnauthorizedException } from 'src/modules/ai/query/exceptions';
68

79
@Injectable()
810
export class LocalAiQueryAuthProvider extends AiQueryAuthProvider {
@@ -22,4 +24,17 @@ export class LocalAiQueryAuthProvider extends AiQueryAuthProvider {
2224
accountId: `${session.user.currentAccountId}`,
2325
};
2426
}
27+
28+
async callWithAuthRetry(sessionMetadata: SessionMetadata, fn: () => Promise<any>, retries = 1) {
29+
try {
30+
return await fn();
31+
} catch (e) {
32+
if (retries > 0 && (e instanceof CloudApiUnauthorizedException || e instanceof AiQueryUnauthorizedException)) {
33+
await this.cloudUserApiService.invalidateApiSession(sessionMetadata).catch(() => {});
34+
return this.callWithAuthRetry(sessionMetadata, fn, retries - 1);
35+
}
36+
37+
throw e;
38+
}
39+
}
2540
}

redisinsight/api/src/modules/cloud/user/cloud-user.api.service.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,16 @@ export class CloudUserApiService {
213213
});
214214
}
215215

216+
/**
217+
* Invalidate user SM API session
218+
* @param sessionMetadata
219+
*/
220+
async invalidateApiSession(
221+
sessionMetadata: SessionMetadata,
222+
): Promise<void> {
223+
await this.sessionService.invalidateApiSession(sessionMetadata.sessionId);
224+
}
225+
216226
/**
217227
* Select current account to work with
218228
* @param sessionMetadata

0 commit comments

Comments
 (0)