feat(server): add invalid oauth callback code error handling (#10603)
close CLOUD-130
This commit is contained in:
@@ -301,6 +301,12 @@ export const USER_FRIENDLY_ERRORS = {
|
|||||||
type: 'bad_request',
|
type: 'bad_request',
|
||||||
message: 'Invalid callback state parameter.',
|
message: 'Invalid callback state parameter.',
|
||||||
},
|
},
|
||||||
|
invalid_oauth_callback_code: {
|
||||||
|
type: 'bad_request',
|
||||||
|
args: { status: 'number', body: 'string' },
|
||||||
|
message: ({ status, body }) =>
|
||||||
|
`Invalid callback code parameter, provider response status: ${status} and body: ${body}.`,
|
||||||
|
},
|
||||||
missing_oauth_query_parameter: {
|
missing_oauth_query_parameter: {
|
||||||
type: 'bad_request',
|
type: 'bad_request',
|
||||||
args: { name: 'string' },
|
args: { name: 'string' },
|
||||||
|
|||||||
@@ -105,6 +105,17 @@ export class InvalidOauthCallbackState extends UserFriendlyError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
|
class InvalidOauthCallbackCodeDataType {
|
||||||
|
@Field() status!: number
|
||||||
|
@Field() body!: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidOauthCallbackCode extends UserFriendlyError {
|
||||||
|
constructor(args: InvalidOauthCallbackCodeDataType, message?: string | ((args: InvalidOauthCallbackCodeDataType) => string)) {
|
||||||
|
super('bad_request', 'invalid_oauth_callback_code', message, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ObjectType()
|
||||||
class MissingOauthQueryParameterDataType {
|
class MissingOauthQueryParameterDataType {
|
||||||
@Field() name!: string
|
@Field() name!: string
|
||||||
}
|
}
|
||||||
@@ -820,6 +831,7 @@ export enum ErrorNames {
|
|||||||
UNKNOWN_OAUTH_PROVIDER,
|
UNKNOWN_OAUTH_PROVIDER,
|
||||||
OAUTH_STATE_EXPIRED,
|
OAUTH_STATE_EXPIRED,
|
||||||
INVALID_OAUTH_CALLBACK_STATE,
|
INVALID_OAUTH_CALLBACK_STATE,
|
||||||
|
INVALID_OAUTH_CALLBACK_CODE,
|
||||||
MISSING_OAUTH_QUERY_PARAMETER,
|
MISSING_OAUTH_QUERY_PARAMETER,
|
||||||
OAUTH_ACCOUNT_ALREADY_CONNECTED,
|
OAUTH_ACCOUNT_ALREADY_CONNECTED,
|
||||||
INVALID_EMAIL,
|
INVALID_EMAIL,
|
||||||
@@ -916,5 +928,5 @@ registerEnumType(ErrorNames, {
|
|||||||
export const ErrorDataUnionType = createUnionType({
|
export const ErrorDataUnionType = createUnionType({
|
||||||
name: 'ErrorDataUnion',
|
name: 'ErrorDataUnion',
|
||||||
types: () =>
|
types: () =>
|
||||||
[GraphqlBadRequestDataType, QueryTooLongDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocActionDeniedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotDocNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, CopilotInvalidContextDataType, CopilotContextFileNotSupportedDataType, CopilotFailedToModifyContextDataType, CopilotFailedToMatchContextDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseUpdateParamsDataType, WorkspaceMembersExceedLimitToDowngradeDataType, UnsupportedClientVersionDataType] as const,
|
[GraphqlBadRequestDataType, QueryTooLongDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, InvalidOauthCallbackCodeDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocActionDeniedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotDocNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, CopilotInvalidContextDataType, CopilotContextFileNotSupportedDataType, CopilotFailedToModifyContextDataType, CopilotFailedToMatchContextDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseUpdateParamsDataType, WorkspaceMembersExceedLimitToDowngradeDataType, UnsupportedClientVersionDataType] as const,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
|
Logger,
|
||||||
Post,
|
Post,
|
||||||
|
type RawBodyRequest,
|
||||||
Req,
|
Req,
|
||||||
Res,
|
Res,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
@@ -27,6 +29,8 @@ import { OAuthService } from './service';
|
|||||||
|
|
||||||
@Controller('/api/oauth')
|
@Controller('/api/oauth')
|
||||||
export class OAuthController {
|
export class OAuthController {
|
||||||
|
private readonly logger = new Logger(OAuthController.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly auth: AuthService,
|
private readonly auth: AuthService,
|
||||||
private readonly oauth: OAuthService,
|
private readonly oauth: OAuthService,
|
||||||
@@ -69,7 +73,7 @@ export class OAuthController {
|
|||||||
@Post('/callback')
|
@Post('/callback')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
async callback(
|
async callback(
|
||||||
@Req() req: Request,
|
@Req() req: RawBodyRequest<Request>,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
@Body('code') code?: string,
|
@Body('code') code?: string,
|
||||||
@Body('state') stateStr?: string
|
@Body('state') stateStr?: string
|
||||||
@@ -102,7 +106,20 @@ export class OAuthController {
|
|||||||
throw new UnknownOauthProvider({ name: state.provider ?? 'unknown' });
|
throw new UnknownOauthProvider({ name: state.provider ?? 'unknown' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = await provider.getToken(code);
|
let tokens: Tokens;
|
||||||
|
try {
|
||||||
|
tokens = await provider.getToken(code);
|
||||||
|
} catch (err) {
|
||||||
|
let rayBodyString = '';
|
||||||
|
if (req.rawBody) {
|
||||||
|
// only log the first 4096 bytes of the raw body
|
||||||
|
rayBodyString = req.rawBody.subarray(0, 4096).toString('utf-8');
|
||||||
|
}
|
||||||
|
this.logger.warn(
|
||||||
|
`Error getting oauth token for ${state.provider}, callback code: ${code}, stateStr: ${stateStr}, rawBody: ${rayBodyString}, error: ${err}`
|
||||||
|
);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
const externAccount = await provider.getUser(tokens.accessToken);
|
const externAccount = await provider.getUser(tokens.accessToken);
|
||||||
const user = await this.loginFromOauth(
|
const user = await this.loginFromOauth(
|
||||||
state.provider,
|
state.provider,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { Config, URLHelper } from '../../../base';
|
import { Config, InvalidOauthCallbackCode, URLHelper } from '../../../base';
|
||||||
import { OAuthProviderName } from '../config';
|
import { OAuthProviderName } from '../config';
|
||||||
import { AutoRegisteredOAuthProvider } from '../register';
|
import { AutoRegisteredOAuthProvider } from '../register';
|
||||||
|
|
||||||
@@ -64,10 +64,12 @@ export class GithubOAuthProvider extends AutoRegisteredOAuthProvider {
|
|||||||
scope: ghToken.scope,
|
scope: ghToken.scope,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
const body = await response.text();
|
||||||
|
if (response.status < 500) {
|
||||||
|
throw new InvalidOauthCallbackCode({ status: response.status, body });
|
||||||
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Server responded with non-success code ${
|
`Server responded with non-success status ${response.status}, body: ${body}`
|
||||||
response.status
|
|
||||||
}, ${JSON.stringify(await response.json())}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { Config, URLHelper } from '../../../base';
|
import { Config, InvalidOauthCallbackCode, URLHelper } from '../../../base';
|
||||||
import { OAuthProviderName } from '../config';
|
import { OAuthProviderName } from '../config';
|
||||||
import { AutoRegisteredOAuthProvider } from '../register';
|
import { AutoRegisteredOAuthProvider } from '../register';
|
||||||
|
|
||||||
@@ -69,10 +69,12 @@ export class GoogleOAuthProvider extends AutoRegisteredOAuthProvider {
|
|||||||
scope: ghToken.scope,
|
scope: ghToken.scope,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
const body = await response.text();
|
||||||
|
if (response.status < 500) {
|
||||||
|
throw new InvalidOauthCallbackCode({ status: response.status, body });
|
||||||
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Server responded with non-success code ${
|
`Server responded with non-success status ${response.status}, body: ${body}`
|
||||||
response.status
|
|
||||||
}, ${JSON.stringify(await response.json())}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ type EditorType {
|
|||||||
name: String!
|
name: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotContextFileNotSupportedDataType | CopilotDocNotFoundDataType | CopilotFailedToMatchContextDataType | CopilotFailedToModifyContextDataType | CopilotInvalidContextDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocActionDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedClientVersionDataType | UnsupportedSubscriptionPlanDataType | VersionRejectedDataType | WorkspaceMembersExceedLimitToDowngradeDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType
|
union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotContextFileNotSupportedDataType | CopilotDocNotFoundDataType | CopilotFailedToMatchContextDataType | CopilotFailedToModifyContextDataType | CopilotInvalidContextDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocActionDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType | InvalidOauthCallbackCodeDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedClientVersionDataType | UnsupportedSubscriptionPlanDataType | VersionRejectedDataType | WorkspaceMembersExceedLimitToDowngradeDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType
|
||||||
|
|
||||||
enum ErrorNames {
|
enum ErrorNames {
|
||||||
ACCESS_DENIED
|
ACCESS_DENIED
|
||||||
@@ -382,6 +382,7 @@ enum ErrorNames {
|
|||||||
INVALID_LICENSE_SESSION_ID
|
INVALID_LICENSE_SESSION_ID
|
||||||
INVALID_LICENSE_TO_ACTIVATE
|
INVALID_LICENSE_TO_ACTIVATE
|
||||||
INVALID_LICENSE_UPDATE_PARAMS
|
INVALID_LICENSE_UPDATE_PARAMS
|
||||||
|
INVALID_OAUTH_CALLBACK_CODE
|
||||||
INVALID_OAUTH_CALLBACK_STATE
|
INVALID_OAUTH_CALLBACK_STATE
|
||||||
INVALID_PASSWORD_LENGTH
|
INVALID_PASSWORD_LENGTH
|
||||||
INVALID_RUNTIME_CONFIG_TYPE
|
INVALID_RUNTIME_CONFIG_TYPE
|
||||||
@@ -503,6 +504,11 @@ type InvalidLicenseUpdateParamsDataType {
|
|||||||
reason: String!
|
reason: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InvalidOauthCallbackCodeDataType {
|
||||||
|
body: String!
|
||||||
|
status: Int!
|
||||||
|
}
|
||||||
|
|
||||||
type InvalidPasswordLengthDataType {
|
type InvalidPasswordLengthDataType {
|
||||||
max: Int!
|
max: Int!
|
||||||
min: Int!
|
min: Int!
|
||||||
|
|||||||
Reference in New Issue
Block a user