diff --git a/redisinsight/api/src/__mocks__/cloud-auth.ts b/redisinsight/api/src/__mocks__/cloud-auth.ts index 599a2668ef..84001c4967 100644 --- a/redisinsight/api/src/__mocks__/cloud-auth.ts +++ b/redisinsight/api/src/__mocks__/cloud-auth.ts @@ -176,14 +176,19 @@ export const mockCloudAuthGithubAuthUrl = `&${new URLSearchParams({ scope: mockCloudAuthGithubRequest.scopes.join(' ') }).toString()}` + '&prompt=login'; +export const mockCloudIdToken = 'id_token_p6vA6A5tF36Jf6twH2cBOqtt7n'; +export const mockCloudIdTokenNew = 'id_token_p6vA6A5tF36Jf6twH2cBOqtt7n-new'; + export const mockTokenResponse = { access_token: mockCloudAccessToken, refresh_token: mockCloudRefreshToken, + id_token: mockCloudIdToken, }; export const mockTokenResponseNew = { access_token: mockCloudAccessTokenNew, refresh_token: mockCloudRefreshTokenNew, + id_token: mockCloudIdTokenNew, }; export const mockCloudAuthGoogleCallbackQueryObject = { diff --git a/redisinsight/api/src/__mocks__/cloud-session.ts b/redisinsight/api/src/__mocks__/cloud-session.ts index cd060e8adf..f7a655fc17 100644 --- a/redisinsight/api/src/__mocks__/cloud-session.ts +++ b/redisinsight/api/src/__mocks__/cloud-session.ts @@ -12,6 +12,7 @@ export const mockCloudApiCsrfToken: ICloudApiCsrfToken = { export const mockCloudApiAuthDto: ICloudApiCredentials = { accessToken: 'at_p6vA6A5tF36Jf6twH2cBOqtt7n', refreshToken: 'rt_p6vA6A5tF36Jf6twH2cBOqtt7n', + idToken: 'id_token_p6vA6A5tF36Jf6twH2cBOqtt7n', idpType: CloudAuthIdpType.Google, csrf: mockCloudApiCsrfToken.csrf_token, apiSessionId: 'asid_p6v-A6A5tF36J-f6twH2cB!@#$_^&*()Oqtt7n', diff --git a/redisinsight/api/src/__mocks__/cloud-user.ts b/redisinsight/api/src/__mocks__/cloud-user.ts index a92d2a39c8..d4d01273c0 100644 --- a/redisinsight/api/src/__mocks__/cloud-user.ts +++ b/redisinsight/api/src/__mocks__/cloud-user.ts @@ -89,6 +89,7 @@ export const mockCloudApiHeaders = { authorization: `Bearer ${mockCloudApiAuthDto.accessToken}`, 'x-csrf-token': mockCloudApiAuthDto.csrf, cookie: `JSESSIONID=${mockCloudApiAuthDto.apiSessionId}`, + 'Sm-Id-Token': mockCloudApiAuthDto.idToken, }, }; diff --git a/redisinsight/api/src/modules/cloud/auth/cloud-auth.service.spec.ts b/redisinsight/api/src/modules/cloud/auth/cloud-auth.service.spec.ts index 57eb8c9395..30a7219d19 100644 --- a/redisinsight/api/src/modules/cloud/auth/cloud-auth.service.spec.ts +++ b/redisinsight/api/src/modules/cloud/auth/cloud-auth.service.spec.ts @@ -283,6 +283,37 @@ describe('CloudAuthService', () => { ); expect(service['authRequests'].size).toEqual(0); }); + it('should store idToken in session when present in token response', async () => { + expect(service['authRequests'].size).toEqual(1); + await service['callback'](mockCloudAuthGoogleCallbackQueryObject); + expect(sessionService.updateSessionData).toHaveBeenCalledWith( + mockCloudAuthGoogleRequest.sessionMetadata.sessionId, + { + accessToken: mockTokenResponse.access_token, + refreshToken: mockTokenResponse.refresh_token, + idToken: mockTokenResponse.id_token, + idpType: mockCloudAuthGoogleRequest.idpType, + }, + ); + }); + it('should handle missing idToken gracefully', async () => { + const tokenResponseWithoutIdToken = { + access_token: mockCloudAccessTokenNew, + refresh_token: mockCloudRefreshTokenNew, + }; + spy.mockResolvedValue(tokenResponseWithoutIdToken); + expect(service['authRequests'].size).toEqual(1); + await service['callback'](mockCloudAuthGoogleCallbackQueryObject); + expect(sessionService.updateSessionData).toHaveBeenCalledWith( + mockCloudAuthGoogleRequest.sessionMetadata.sessionId, + { + accessToken: tokenResponseWithoutIdToken.access_token, + refreshToken: tokenResponseWithoutIdToken.refresh_token, + idToken: undefined, + idpType: mockCloudAuthGoogleRequest.idpType, + }, + ); + }); it('should throw an error if error field in query parameters (CloudOauthMisconfigurationException)', async () => { expect(service['authRequests'].size).toEqual(1); await expect( @@ -396,6 +427,57 @@ describe('CloudAuthService', () => { { accessToken: mockCloudAccessTokenNew, refreshToken: mockCloudRefreshTokenNew, + idToken: mockTokenResponseNew.id_token, + idpType: mockCloudApiAuthDto.idpType, + csrf: null, + apiSessionId: null, + }, + ); + }); + + it('should store idToken in session when present in token response', async () => { + mockedAxios.post.mockResolvedValueOnce({ data: mockTokenResponseNew }); + + await service['renewTokens']( + mockSessionMetadata, + mockCloudApiAuthDto.idpType, + mockCloudApiAuthDto.refreshToken, + ); + + expect(sessionService.updateSessionData).toHaveBeenCalledWith( + mockSessionMetadata.sessionId, + { + accessToken: mockCloudAccessTokenNew, + refreshToken: mockCloudRefreshTokenNew, + idToken: mockTokenResponseNew.id_token, + idpType: mockCloudApiAuthDto.idpType, + csrf: null, + apiSessionId: null, + }, + ); + }); + + it('should handle missing idToken gracefully', async () => { + const tokenResponseWithoutIdToken = { + access_token: mockCloudAccessTokenNew, + refresh_token: mockCloudRefreshTokenNew, + }; + mockedAxios.post.mockResolvedValueOnce({ + data: tokenResponseWithoutIdToken, + }); + + await service['renewTokens']( + mockSessionMetadata, + mockCloudApiAuthDto.idpType, + mockCloudApiAuthDto.refreshToken, + ); + + expect(sessionService.updateSessionData).toHaveBeenCalledWith( + mockSessionMetadata.sessionId, + { + accessToken: tokenResponseWithoutIdToken.access_token, + refreshToken: tokenResponseWithoutIdToken.refresh_token, + idToken: undefined, idpType: mockCloudApiAuthDto.idpType, csrf: null, apiSessionId: null, diff --git a/redisinsight/api/src/modules/cloud/auth/cloud-auth.service.ts b/redisinsight/api/src/modules/cloud/auth/cloud-auth.service.ts index 4ba2bcae0d..f88df092d7 100644 --- a/redisinsight/api/src/modules/cloud/auth/cloud-auth.service.ts +++ b/redisinsight/api/src/modules/cloud/auth/cloud-auth.service.ts @@ -225,6 +225,7 @@ export class CloudAuthService { { accessToken: tokens.access_token, refreshToken: tokens.refresh_token, + idToken: tokens.id_token, idpType: authRequest.idpType, }, ); @@ -344,6 +345,7 @@ export class CloudAuthService { await this.sessionService.updateSessionData(sessionMetadata.sessionId, { accessToken: data.access_token, refreshToken: data.refresh_token, + idToken: data.id_token, idpType, csrf: null, apiSessionId: null, diff --git a/redisinsight/api/src/modules/cloud/common/models/api.interface.ts b/redisinsight/api/src/modules/cloud/common/models/api.interface.ts index 4e714d57aa..7f6abee6e9 100644 --- a/redisinsight/api/src/modules/cloud/common/models/api.interface.ts +++ b/redisinsight/api/src/modules/cloud/common/models/api.interface.ts @@ -3,6 +3,7 @@ import { CloudAuthIdpType } from 'src/modules/cloud/auth/models'; export interface ICloudApiCredentials { accessToken?: string; refreshToken?: string; + idToken?: string; idpType?: CloudAuthIdpType; apiSessionId?: string; csrf?: string; diff --git a/redisinsight/api/src/modules/cloud/common/providers/cloud.api.provider.spec.ts b/redisinsight/api/src/modules/cloud/common/providers/cloud.api.provider.spec.ts index 43fd9c9b12..e60697c9a7 100644 --- a/redisinsight/api/src/modules/cloud/common/providers/cloud.api.provider.spec.ts +++ b/redisinsight/api/src/modules/cloud/common/providers/cloud.api.provider.spec.ts @@ -73,6 +73,26 @@ const getHeadersTests = [ input: { csrf: 'csrf-token' }, expected: { ...mockDefaultCloudApiHeaders, 'x-csrf-token': 'csrf-token' }, }, + { + input: { idToken: 'id-token-value' }, + expected: { + ...mockDefaultCloudApiHeaders, + 'Sm-Id-Token': 'id-token-value', + }, + }, + { + input: { + accessToken: 'jwt-token', + idToken: 'id-token-value', + csrf: 'csrf-token', + }, + expected: { + ...mockDefaultCloudApiHeaders, + authorization: 'Bearer jwt-token', + 'x-csrf-token': 'csrf-token', + 'Sm-Id-Token': 'id-token-value', + }, + }, ]; const mockedResult = 'mockedResult'; diff --git a/redisinsight/api/src/modules/cloud/common/providers/cloud.api.provider.ts b/redisinsight/api/src/modules/cloud/common/providers/cloud.api.provider.ts index d8ebe887d0..6b4ed30a4e 100644 --- a/redisinsight/api/src/modules/cloud/common/providers/cloud.api.provider.ts +++ b/redisinsight/api/src/modules/cloud/common/providers/cloud.api.provider.ts @@ -74,6 +74,10 @@ export class CloudApiProvider { headers['x-csrf-token'] = credentials.csrf; } + if (credentials?.idToken) { + headers['Sm-Id-Token'] = credentials.idToken; + } + return { headers, }; diff --git a/redisinsight/api/src/modules/cloud/session/models/cloud-session.ts b/redisinsight/api/src/modules/cloud/session/models/cloud-session.ts index 7563984a91..39b04aa43c 100644 --- a/redisinsight/api/src/modules/cloud/session/models/cloud-session.ts +++ b/redisinsight/api/src/modules/cloud/session/models/cloud-session.ts @@ -9,6 +9,9 @@ export class CloudSession { @Expose() refreshToken?: string; + @Expose() + idToken?: string; + @Expose() idpType?: CloudAuthIdpType;