feat(server): add invalid oauth callback code error handling (#10603)

close CLOUD-130
This commit is contained in:
fengmk2
2025-03-05 06:16:59 +00:00
parent 0b8fa7904d
commit b88113a2d1
6 changed files with 57 additions and 12 deletions

View File

@@ -301,6 +301,12 @@ export const USER_FRIENDLY_ERRORS = {
type: 'bad_request',
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: {
type: 'bad_request',
args: { name: 'string' },

View File

@@ -105,6 +105,17 @@ export class InvalidOauthCallbackState extends UserFriendlyError {
}
}
@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 {
@Field() name!: string
}
@@ -820,6 +831,7 @@ export enum ErrorNames {
UNKNOWN_OAUTH_PROVIDER,
OAUTH_STATE_EXPIRED,
INVALID_OAUTH_CALLBACK_STATE,
INVALID_OAUTH_CALLBACK_CODE,
MISSING_OAUTH_QUERY_PARAMETER,
OAUTH_ACCOUNT_ALREADY_CONNECTED,
INVALID_EMAIL,
@@ -916,5 +928,5 @@ registerEnumType(ErrorNames, {
export const ErrorDataUnionType = createUnionType({
name: 'ErrorDataUnion',
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,
});

View File

@@ -3,7 +3,9 @@ import {
Controller,
HttpCode,
HttpStatus,
Logger,
Post,
type RawBodyRequest,
Req,
Res,
} from '@nestjs/common';
@@ -27,6 +29,8 @@ import { OAuthService } from './service';
@Controller('/api/oauth')
export class OAuthController {
private readonly logger = new Logger(OAuthController.name);
constructor(
private readonly auth: AuthService,
private readonly oauth: OAuthService,
@@ -69,7 +73,7 @@ export class OAuthController {
@Post('/callback')
@HttpCode(HttpStatus.OK)
async callback(
@Req() req: Request,
@Req() req: RawBodyRequest<Request>,
@Res() res: Response,
@Body('code') code?: string,
@Body('state') stateStr?: string
@@ -102,7 +106,20 @@ export class OAuthController {
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 user = await this.loginFromOauth(
state.provider,

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { Config, URLHelper } from '../../../base';
import { Config, InvalidOauthCallbackCode, URLHelper } from '../../../base';
import { OAuthProviderName } from '../config';
import { AutoRegisteredOAuthProvider } from '../register';
@@ -64,10 +64,12 @@ export class GithubOAuthProvider extends AutoRegisteredOAuthProvider {
scope: ghToken.scope,
};
} else {
const body = await response.text();
if (response.status < 500) {
throw new InvalidOauthCallbackCode({ status: response.status, body });
}
throw new Error(
`Server responded with non-success code ${
response.status
}, ${JSON.stringify(await response.json())}`
`Server responded with non-success status ${response.status}, body: ${body}`
);
}
}

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { Config, URLHelper } from '../../../base';
import { Config, InvalidOauthCallbackCode, URLHelper } from '../../../base';
import { OAuthProviderName } from '../config';
import { AutoRegisteredOAuthProvider } from '../register';
@@ -69,10 +69,12 @@ export class GoogleOAuthProvider extends AutoRegisteredOAuthProvider {
scope: ghToken.scope,
};
} else {
const body = await response.text();
if (response.status < 500) {
throw new InvalidOauthCallbackCode({ status: response.status, body });
}
throw new Error(
`Server responded with non-success code ${
response.status
}, ${JSON.stringify(await response.json())}`
`Server responded with non-success status ${response.status}, body: ${body}`
);
}
}

View File

@@ -324,7 +324,7 @@ type EditorType {
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 {
ACCESS_DENIED
@@ -382,6 +382,7 @@ enum ErrorNames {
INVALID_LICENSE_SESSION_ID
INVALID_LICENSE_TO_ACTIVATE
INVALID_LICENSE_UPDATE_PARAMS
INVALID_OAUTH_CALLBACK_CODE
INVALID_OAUTH_CALLBACK_STATE
INVALID_PASSWORD_LENGTH
INVALID_RUNTIME_CONFIG_TYPE
@@ -503,6 +504,11 @@ type InvalidLicenseUpdateParamsDataType {
reason: String!
}
type InvalidOauthCallbackCodeDataType {
body: String!
status: Int!
}
type InvalidPasswordLengthDataType {
max: Int!
min: Int!