Skip to content
Merged
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 redisinsight/api/src/constants/custom-error-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export enum CustomErrorCodes {
CloudOauthUnknownAuthorizationRequest = 11_007,
CloudOauthUnexpectedError = 11_008,
CloudOauthMissedRequiredData = 11_009,
CloudOauthCanceled = 11_010,
CloudCapiUnauthorized = 11_021,
CloudCapiKeyUnauthorized = 11_022,
CloudCapiKeyNotFound = 11_023,
Expand Down
3 changes: 2 additions & 1 deletion redisinsight/api/src/constants/error-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@ export default {

CLOUD_CAPI_KEY_UNAUTHORIZED: 'Unable to authorize such CAPI key',

CLOUD_OAUTH_CANCELED: 'Authorization request was canceled.',
CLOUD_OAUTH_MISCONFIGURATION: 'Authorization server misconfiguration.',
CLOUD_OAUTH_GITHUB_EMAIL_PERMISSION: 'Unable to get an email from the GitHub account. Make sure that it is available.',
CLOUD_OAUTH_MISSED_REQUIRED_DATA: 'Unable to get required data from the user profile.',
CLOUD_OAUTH_GITHUB_UNKNOWN_AUTHORIZATION_REQUEST: 'Unknown authorization request.',
CLOUD_OAUTH_UNKNOWN_AUTHORIZATION_REQUEST: 'Unknown authorization request.',
CLOUD_OAUTH_UNEXPECTED_ERROR: 'Unexpected error.',

CLOUD_JOB_UNEXPECTED_ERROR: 'Unexpected error occurred',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,10 @@ describe('CloudAuthService', () => {
await expect(service['callback'](
{
...mockCloudAuthGoogleCallbackQueryObject,
error: 'bad request',
error_description: 'Some properties are missing: email and lastName',
error: 'access_denied',
error_description: 'Some required properties are missing: email and lastName',
},
)).rejects.toThrow(new CloudOauthMissedRequiredDataException('Some properties are missing: email and lastName'));
)).rejects.toThrow(new CloudOauthMissedRequiredDataException('Some required properties are missing: email and lastName'));
});
it('should throw an error if request not found', async () => {
expect(service['authRequests'].size).toEqual(1);
Expand Down
59 changes: 41 additions & 18 deletions redisinsight/api/src/modules/cloud/auth/cloud-auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { CloudSessionService } from 'src/modules/cloud/session/cloud-session.ser
import { GithubIdpCloudAuthStrategy } from 'src/modules/cloud/auth/auth-strategy/github-idp.cloud.auth-strategy';
import { wrapHttpError } from 'src/common/utils';
import {
CloudOauthCanceledException,
CloudOauthGithubEmailPermissionException,
CloudOauthMisconfigurationException, CloudOauthMissedRequiredDataException,
CloudOauthUnexpectedErrorException,
CloudOauthUnknownAuthorizationRequestException,
} from 'src/modules/cloud/auth/exceptions';
import { CloudAuthRequestInfo, CloudAuthResponse, CloudAuthStatus } from 'src/modules/cloud/auth/models';
Expand All @@ -32,14 +35,30 @@ export class CloudAuthService {
private readonly eventEmitter: EventEmitter2,
) {}

static getAuthorizationServerRedirectError(query: { error_description: string }) {
if (query?.error_description?.indexOf('properties are missing') > -1) {
return new CloudOauthMissedRequiredDataException(query.error_description, {
description: query.error_description,
});
static getAuthorizationServerRedirectError(
query: { error_description: string, error: string },
authRequest?: CloudAuthRequest,
) {
if (query?.error_description?.indexOf('canceled') > -1) {
return new CloudOauthCanceledException();
}

if (
query?.error_description?.indexOf('propert') > -1
&& query?.error_description?.indexOf('required') > -1
&& query?.error_description?.indexOf('miss') > -1
) {
return (
authRequest?.idpType === CloudAuthIdpType.GitHub
&& query?.error_description?.indexOf('email') > -1
)
? new CloudOauthGithubEmailPermissionException(query.error_description)
: new CloudOauthMissedRequiredDataException(query.error_description, {
description: query.error_description,
});
}

return new CloudOauthMisconfigurationException(undefined, {
return new CloudOauthUnexpectedErrorException(undefined, {
description: query.error_description,
});
}
Expand Down Expand Up @@ -68,17 +87,21 @@ export class CloudAuthService {
callback?: Function,
},
): Promise<string> {
const authRequest: any = await this.getAuthStrategy(options?.strategy).generateAuthRequest(sessionMetadata);
authRequest.callback = options?.callback;
authRequest.action = options?.action;
try {
const authRequest: any = await this.getAuthStrategy(options?.strategy).generateAuthRequest(sessionMetadata);
authRequest.callback = options?.callback;
authRequest.action = options?.action;

// based on requirements we must support only single auth request at the moment
// and logout user before
await this.logout(sessionMetadata);
this.authRequests.clear();
this.authRequests.set(authRequest.state, authRequest);
// based on requirements we must support only single auth request at the moment
// and logout user before
await this.logout(sessionMetadata);
this.authRequests.clear();
this.authRequests.set(authRequest.state, authRequest);

return CloudAuthStrategy.generateAuthUrl(authRequest).toString();
return CloudAuthStrategy.generateAuthUrl(authRequest).toString();
} catch (e) {
throw new CloudOauthMisconfigurationException();
}
}

/**
Expand Down Expand Up @@ -137,12 +160,12 @@ export class CloudAuthService {
throw new CloudOauthUnknownAuthorizationRequestException();
}

const authRequest = this.authRequests.get(query.state);

if (query?.error) {
throw CloudAuthService.getAuthorizationServerRedirectError(query);
throw CloudAuthService.getAuthorizationServerRedirectError(query, authRequest);
}

const authRequest = this.authRequests.get(query.state);

// delete authRequest on this step
// allow to redirect with authorization code only once
this.authRequests.delete(query.state);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { HttpException, HttpExceptionOptions } from '@nestjs/common';
import { CustomErrorCodes } from 'src/constants';
import ERROR_MESSAGES from 'src/constants/error-messages';

export class CloudOauthCanceledException extends HttpException {
constructor(message = ERROR_MESSAGES.CLOUD_OAUTH_CANCELED, options?: HttpExceptionOptions) {
const response = {
message,
statusCode: 499,
error: 'CloudOauthCanceled',
errorCode: CustomErrorCodes.CloudOauthCanceled,
};

super(response, response.statusCode, options);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CustomErrorCodes } from 'src/constants';
import ERROR_MESSAGES from 'src/constants/error-messages';

export class CloudOauthUnknownAuthorizationRequestException extends HttpException {
constructor(message = ERROR_MESSAGES.CLOUD_OAUTH_GITHUB_UNKNOWN_AUTHORIZATION_REQUEST, options?: HttpExceptionOptions) {
constructor(message = ERROR_MESSAGES.CLOUD_OAUTH_UNKNOWN_AUTHORIZATION_REQUEST, options?: HttpExceptionOptions) {
const response = {
message,
statusCode: HttpStatus.BAD_REQUEST,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './cloud-oauth.canceled.exception';
export * from './cloud-oauth.misconfiguration.exception';
export * from './cloud-oauth.github-email-permission.exception';
export * from './cloud-oauth.missed-required-data.exception';
Expand Down
22 changes: 18 additions & 4 deletions redisinsight/desktop/src/lib/cloud/cloud-oauth.handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ import { DEFAULT_SESSION_ID, DEFAULT_USER_ID } from 'apiSrc/common/constants'
import { CloudOauthUnexpectedErrorException } from 'apiSrc/modules/cloud/auth/exceptions'
import { CloudAuthService } from '../../../../api/dist/src/modules/cloud/auth/cloud-auth.service'

export const getOauthIpcErrorResponse = (error: any): { status: CloudAuthStatus.Failed, error: {} } => {
let errorResponse = new CloudOauthUnexpectedErrorException().getResponse()

if (error?.getResponse) {
errorResponse = error.getResponse()
} else if (error instanceof Error) {
errorResponse = new CloudOauthUnexpectedErrorException(error.message).getResponse()
}

return {
status: CloudAuthStatus.Failed,
error: errorResponse,
}
}

export const getTokenCallbackFunction = (webContents: WebContents) => (response: CloudAuthResponse) => {
webContents.send(IpcOnEvent.cloudOauthCallback, response)
webContents.focus()
Expand Down Expand Up @@ -42,10 +57,9 @@ export const initCloudOauthHandlers = () => {
} catch (e) {
log.error(wrapErrorMessageSensitiveData(e as Error))

return {
status: CloudAuthStatus.Failed,
error: (new CloudOauthUnexpectedErrorException()).getResponse(),
}
const [currentWindow] = getWindows().values()

currentWindow?.webContents.send(IpcOnEvent.cloudOauthCallback, getOauthIpcErrorResponse(e))
}
})
}
Expand Down