Skip to content

Commit 362e4c2

Browse files
authored
Merge pull request #3790 from RedisInsight/release/2.56.0
Release/2.56.0
2 parents 6fed1a3 + 8401d97 commit 362e4c2

File tree

116 files changed

+1472
-375
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+1472
-375
lines changed

.circleci/build/release-docker.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
set -e
33

44
HELP="Args:
5-
-v - Semver (2.54.0)
5+
-v - Semver (2.56.0)
66
-d - Build image repository (Ex: -d redisinsight)
77
-r - Target repository (Ex: -r redis/redisinsight)
88
"

configs/webpack.config.main.prod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default merge(baseConfig, {
5656
DEBUG_PROD: false,
5757
START_MINIMIZED: false,
5858
RI_APP_TYPE: 'electron',
59-
RI_AUTO_BOOTSTRAP: false,
59+
RI_AUTO_BOOTSTRAP: 'false',
6060
RI_SERVER_TLS_CERT: process.env.RI_SERVER_TLS_CERT || '',
6161
RI_SERVER_TLS_KEY: process.env.RI_SERVER_TLS_KEY || '',
6262
RI_SERVE_STATICS: false,

redisinsight/api/config/default.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export default {
8080
migrateOldFolders: process.env.RI_MIGRATE_OLD_FOLDERS ? process.env.RI_MIGRATE_OLD_FOLDERS === 'true' : true,
8181
autoBootstrap: process.env.RI_AUTO_BOOTSTRAP ? process.env.RI_AUTO_BOOTSTRAP === 'true' : true,
8282
buildType: process.env.RI_BUILD_TYPE || 'DOCKER_ON_PREMISE',
83-
appVersion: process.env.RI_APP_VERSION || '2.54.0',
83+
appVersion: process.env.RI_APP_VERSION || '2.56.0',
8484
requestTimeout: parseInt(process.env.RI_REQUEST_TIMEOUT, 10) || 25000,
8585
excludeRoutes: [],
8686
excludeAuthRoutes: [],
@@ -266,6 +266,16 @@ export default {
266266
redirectUri: process.env.RI_CLOUD_IDP_GH_REDIRECT_URI || process.env.RI_CLOUD_IDP_REDIRECT_URI,
267267
idp: process.env.RI_CLOUD_IDP_GH_ID,
268268
},
269+
sso: {
270+
authorizeUrl: process.env.RI_CLOUD_IDP_SSO_AUTHORIZE_URL || process.env.RI_CLOUD_IDP_AUTHORIZE_URL,
271+
tokenUrl: process.env.RI_CLOUD_IDP_SSO_TOKEN_URL || process.env.RI_CLOUD_IDP_TOKEN_URL,
272+
revokeTokenUrl: process.env.RI_CLOUD_IDP_SSO_REVOKE_TOKEN_URL || process.env.RI_CLOUD_IDP_REVOKE_TOKEN_URL,
273+
issuer: process.env.RI_CLOUD_IDP_SSO_ISSUER || process.env.RI_CLOUD_IDP_ISSUER,
274+
clientId: process.env.RI_CLOUD_IDP_SSO_CLIENT_ID || process.env.RI_CLOUD_IDP_CLIENT_ID,
275+
redirectUri: process.env.RI_CLOUD_IDP_SSO_REDIRECT_URI || process.env.RI_CLOUD_IDP_REDIRECT_URI,
276+
emailVerificationUri: process.env.RI_CLOUD_IDP_SSO_EMAIL_VERIFICATION_URI || 'saml/okta_idp_id',
277+
idp: process.env.RI_CLOUD_IDP_SSO_ID,
278+
},
269279
},
270280
},
271281
ai: {

redisinsight/api/config/swagger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const SWAGGER_CONFIG: Omit<OpenAPIObject, 'paths'> = {
55
info: {
66
title: 'Redis Insight Backend API',
77
description: 'Redis Insight Backend API',
8-
version: '2.54.0',
8+
version: '2.56.0',
99
},
1010
tags: [],
1111
};

redisinsight/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "redisinsight-api",
3-
"version": "2.54.0",
3+
"version": "2.56.0",
44
"description": "Redis Insight API",
55
"private": true,
66
"author": {

redisinsight/api/src/__mocks__/cloud-auth.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { CloudAuthRequest, CloudAuthResponse, CloudAuthStatus } from 'src/modules/cloud/auth/models';
1+
import {
2+
CloudAuthIdpType, CloudAuthRequest, CloudAuthResponse, CloudAuthStatus,
3+
} from 'src/modules/cloud/auth/models';
24
import { mockSessionMetadata } from 'src/__mocks__/common';
35

46
export const mockCloudAuthCode = 'ac_p6vA6A5tF36Jf6twH2cBOqtt7n';
@@ -43,6 +45,12 @@ export const mockCloudAuthGithubTokenParams = {
4345
idpType: 'github',
4446
};
4547

48+
export const mockCloudAuthSsoTokenParams = {
49+
...mockCloudAuthGoogleTokenParams,
50+
state: 'state_p6vA6A5tF36Jf6twH2cBOqtt7ssp',
51+
idpType: 'sso',
52+
};
53+
4654
export const mockCloudAuthGoogleRequest = Object.assign(new CloudAuthRequest(), {
4755
...mockCloudAuthGoogleTokenParams,
4856
sessionMetadata: {
@@ -52,6 +60,15 @@ export const mockCloudAuthGoogleRequest = Object.assign(new CloudAuthRequest(),
5260
createdAt: new Date(),
5361
});
5462

63+
export const mockCloudAuthSsoRequest = Object.assign(new CloudAuthRequest(), {
64+
...mockCloudAuthGoogleRequest,
65+
...mockCloudAuthSsoTokenParams,
66+
tokenManager: { storage: {} },
67+
createdAt: new Date(),
68+
idpType: CloudAuthIdpType.Sso,
69+
idp: 'idp_p6vA6A5tF36Jf6twH2cBOqtSSO',
70+
});
71+
5572
export const mockCloudAuthGoogleAuthUrl = `${mockCloudAuthGoogleRequest.issuer}`
5673
+ `/${mockCloudAuthGoogleRequest.authorizeUrl}`
5774
+ `?client_id=${mockCloudAuthGoogleRequest.clientId}`
@@ -66,6 +83,20 @@ export const mockCloudAuthGoogleAuthUrl = `${mockCloudAuthGoogleRequest.issuer}`
6683
+ `&${new URLSearchParams({ scope: mockCloudAuthGoogleRequest.scopes.join(' ') }).toString()}`
6784
+ '&prompt=login';
6885

86+
export const mockCloudAuthSsoAuthUrl = `${mockCloudAuthSsoRequest.issuer}`
87+
+ `/${mockCloudAuthSsoRequest.authorizeUrl}`
88+
+ `?client_id=${mockCloudAuthSsoRequest.clientId}`
89+
+ `&${new URLSearchParams({ redirect_uri: mockCloudAuthSsoRequest.redirectUri }).toString()}`
90+
+ `&response_type=${mockCloudAuthSsoRequest.responseType}`
91+
+ `&response_mode=${mockCloudAuthSsoRequest.responseMode}`
92+
+ `&idp=${mockCloudAuthSsoRequest.idp}`
93+
+ `&state=${mockCloudAuthSsoRequest.state}`
94+
+ `&nonce=${mockCloudAuthSsoRequest.nonce}`
95+
+ `&code_challenge_method=${mockCloudAuthSsoRequest.codeChallengeMethod}`
96+
+ `&code_challenge=${mockCloudAuthSsoRequest.codeChallenge}`
97+
+ `&${new URLSearchParams({ scope: mockCloudAuthSsoRequest.scopes.join(' ') }).toString()}`
98+
+ '&prompt=login';
99+
69100
export const mockCloudAuthGoogleTokenUrl = `${mockCloudAuthGoogleRequest.issuer}`
70101
+ `/${mockCloudAuthGoogleRequest.tokenUrl}`
71102
+ `?client_id=${mockCloudAuthGoogleRequest.clientId}`
@@ -83,6 +114,12 @@ export const mockCloudAuthGoogleRevokeTokenUrl = `${mockCloudAuthGoogleRequest.i
83114
+ `&token_type_hint=${mockCloudRevokeRefreshTokenHint}`
84115
+ `&token=${mockCloudRefreshToken}`;
85116

117+
export const mockCloudAuthSsoRevokeTokenUrl = `${mockCloudAuthSsoRequest.issuer}`
118+
+ `/${mockCloudAuthGoogleIdpConfig.revokeTokenUrl}`
119+
+ `?client_id=${mockCloudAuthSsoRequest.clientId}`
120+
+ `&token_type_hint=${mockCloudRevokeRefreshTokenHint}`
121+
+ `&token=${mockCloudRefreshToken}`;
122+
86123
export const mockCloudAuthGoogleRenewTokenUrl = `${mockCloudAuthGoogleRequest.issuer}`
87124
+ `/${mockCloudAuthGoogleIdpConfig.tokenUrl}`
88125
+ `?client_id=${mockCloudAuthGoogleRequest.clientId}`
@@ -91,6 +128,14 @@ export const mockCloudAuthGoogleRenewTokenUrl = `${mockCloudAuthGoogleRequest.is
91128
+ `&${new URLSearchParams({ scope: mockCloudAuthGoogleRequest.scopes.join(' ') }).toString()}`
92129
+ `&refresh_token=${mockCloudRefreshToken}`;
93130

131+
export const mockCloudAuthSsoRenewTokenUrl = `${mockCloudAuthSsoRequest.issuer}`
132+
+ `/${mockCloudAuthGoogleIdpConfig.tokenUrl}`
133+
+ `?client_id=${mockCloudAuthSsoRequest.clientId}`
134+
+ '&grant_type=refresh_token'
135+
+ `&${new URLSearchParams({ redirect_uri: mockCloudAuthSsoRequest.redirectUri }).toString()}`
136+
+ `&${new URLSearchParams({ scope: mockCloudAuthSsoRequest.scopes.join(' ') }).toString()}`
137+
+ `&refresh_token=${mockCloudRefreshToken}`;
138+
94139
export const mockCloudAuthGithubRequest = Object.assign(new CloudAuthRequest(), {
95140
...mockCloudAuthGithubTokenParams,
96141
sessionMetadata: {
@@ -160,6 +205,12 @@ export const mockGoogleIdpCloudAuthStrategy = jest.fn(() => ({
160205
generateRenewTokensUrl: jest.fn().mockReturnValue(new URL(mockCloudAuthGoogleRenewTokenUrl)),
161206
}));
162207

208+
export const mockSsoIdpCloudAuthStrategy = jest.fn(() => ({
209+
generateAuthRequest: jest.fn().mockResolvedValue(mockCloudAuthSsoRequest),
210+
generateRevokeTokensUrl: jest.fn().mockReturnValue(new URL(mockCloudAuthSsoRevokeTokenUrl)),
211+
generateRenewTokensUrl: jest.fn().mockReturnValue(new URL(mockCloudAuthSsoRenewTokenUrl)),
212+
}));
213+
163214
export const mockCloudAuthService = jest.fn(() => ({
164215
renewTokens: jest.fn().mockResolvedValue(undefined),
165216
}));

redisinsight/api/src/constants/custom-error-codes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export enum CustomErrorCodes {
1414
CloudOauthUnexpectedError = 11_008,
1515
CloudOauthMissedRequiredData = 11_009,
1616
CloudOauthCanceled = 11_010,
17+
CloudOauthSsoUnsupportedEmail = 11_011,
1718
CloudCapiUnauthorized = 11_021,
1819
CloudCapiKeyUnauthorized = 11_022,
1920
CloudCapiKeyNotFound = 11_023,

redisinsight/api/src/constants/error-messages.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export default {
8989
CLOUD_OAUTH_CANCELED: 'Authorization request was canceled.',
9090
CLOUD_OAUTH_MISCONFIGURATION: 'Authorization server misconfiguration.',
9191
CLOUD_OAUTH_GITHUB_EMAIL_PERMISSION: 'Unable to get an email from the GitHub account. Make sure that it is available.',
92+
CLOUD_OAUTH_SSO_UNSUPPORTED_EMAIL: 'Invalid email.',
9293
CLOUD_OAUTH_MISSED_REQUIRED_DATA: 'Unable to get required data from the user profile.',
9394
CLOUD_OAUTH_UNKNOWN_AUTHORIZATION_REQUEST: 'Unknown authorization request.',
9495
CLOUD_OAUTH_UNEXPECTED_ERROR: 'Unexpected error.',

redisinsight/api/src/modules/cloud/auth/auth-strategy/cloud-auth.strategy.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable } from '@nestjs/common';
2-
import { CloudAuthRequest } from 'src/modules/cloud/auth/models/cloud-auth-request';
2+
import { CloudAuthRequest, CloudAuthRequestOptions } from 'src/modules/cloud/auth/models/cloud-auth-request';
33
import { SessionMetadata } from 'src/common/models';
44
import { OktaAuth } from '@okta/okta-auth-js';
55
import { plainToClass } from 'class-transformer';
@@ -11,7 +11,10 @@ export abstract class CloudAuthStrategy {
1111
/**
1212
* Create and store auth request params
1313
*/
14-
async generateAuthRequest(sessionMetadata: SessionMetadata): Promise<CloudAuthRequest> {
14+
async generateAuthRequest(
15+
sessionMetadata: SessionMetadata,
16+
_options?: CloudAuthRequestOptions,
17+
): Promise<CloudAuthRequest> {
1518
const authClient = new OktaAuth(this.config);
1619
const tokenParams = await authClient.token.prepareTokenParams(this.config);
1720

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import axios from 'axios';
2+
import { Test, TestingModule } from '@nestjs/testing';
3+
import { EventEmitter2 } from '@nestjs/event-emitter';
4+
import { mockSessionMetadata } from 'src/__mocks__';
5+
import { mockCloudAuthSsoRequest, mockCloudAuthSsoTokenParams, mockOktaAuthClient } from 'src/__mocks__/cloud-auth';
6+
import { OktaAuth } from '@okta/okta-auth-js';
7+
import { SsoIdpCloudAuthStrategy } from 'src/modules/cloud/auth/auth-strategy/sso-idp.cloud.auth-strategy';
8+
import { CloudAuthIdpType } from 'src/modules/cloud/auth/models';
9+
import {
10+
CloudOauthSsoUnsupportedEmailException
11+
} from 'src/modules/cloud/auth/exceptions/cloud-oauth.sso-unsupported-email.exception';
12+
13+
jest.mock('@okta/okta-auth-js');
14+
const mockedAxios = axios as jest.Mocked<typeof axios>;
15+
jest.mock('axios');
16+
17+
describe('CloudAuthStrategy', () => {
18+
let ssoStrategy: SsoIdpCloudAuthStrategy;
19+
20+
beforeEach(async () => {
21+
jest.clearAllMocks();
22+
jest.mock('axios', () => mockedAxios);
23+
(OktaAuth as any).mockReturnValueOnce(mockOktaAuthClient);
24+
25+
const module: TestingModule = await Test.createTestingModule({
26+
providers: [
27+
EventEmitter2,
28+
SsoIdpCloudAuthStrategy,
29+
],
30+
}).compile();
31+
32+
ssoStrategy = await module.get(SsoIdpCloudAuthStrategy);
33+
});
34+
35+
describe('generateAuthRequest', () => {
36+
it('Check that Sso auth request is generated', async () => {
37+
mockOktaAuthClient.token.prepareTokenParams.mockResolvedValueOnce(mockCloudAuthSsoTokenParams);
38+
mockedAxios.get.mockResolvedValue({ data: mockCloudAuthSsoRequest.idp });
39+
40+
expect(await ssoStrategy.generateAuthRequest(mockSessionMetadata, {
41+
strategy: CloudAuthIdpType.Sso,
42+
data: {
43+
44+
},
45+
})).toEqual({
46+
...mockCloudAuthSsoRequest,
47+
createdAt: expect.anything(),
48+
});
49+
});
50+
it('should throw CloudOauthSsoUnsupportedEmailException in case of idp check error ', async () => {
51+
mockOktaAuthClient.token.prepareTokenParams.mockResolvedValueOnce(mockCloudAuthSsoTokenParams);
52+
mockedAxios.get.mockRejectedValueOnce(new Error());
53+
54+
await expect(ssoStrategy.generateAuthRequest(mockSessionMetadata, {
55+
strategy: CloudAuthIdpType.Sso,
56+
})).rejects.toThrow(CloudOauthSsoUnsupportedEmailException);
57+
});
58+
it('should throw CloudOauthSsoUnsupportedEmailException in case of idp check error ', async () => {
59+
mockOktaAuthClient.token.prepareTokenParams.mockResolvedValueOnce(mockCloudAuthSsoTokenParams);
60+
mockedAxios.get.mockRejectedValueOnce(new Error());
61+
62+
await expect(ssoStrategy.generateAuthRequest(mockSessionMetadata))
63+
.rejects.toThrow(CloudOauthSsoUnsupportedEmailException);
64+
});
65+
});
66+
});

0 commit comments

Comments
 (0)