chore(core): Provide details on access token exchange error (#17506)

This commit is contained in:
Andreas Fitzek
2025-07-22 09:45:53 +02:00
committed by GitHub
parent 0db24ce71b
commit e8056515c3
2 changed files with 32 additions and 2 deletions

View File

@@ -42,8 +42,9 @@ export class ResponseError extends Error {
readonly status: number, readonly status: number,
readonly body: unknown, readonly body: unknown,
readonly code = 'ESTATUS', readonly code = 'ESTATUS',
readonly message = `HTTP status ${status}`,
) { ) {
super(`HTTP status ${status}`); super(message);
} }
} }
@@ -133,6 +134,11 @@ export class ClientOAuth2 {
return qs.parse(body) as T; return qs.parse(body) as T;
} }
throw new Error(`Unsupported content type: ${contentType}`); throw new ResponseError(
response.status,
body,
undefined,
`Unsupported content type: ${contentType}`,
);
} }
} }

View File

@@ -243,6 +243,30 @@ describe('OAuth2CredentialController', () => {
}); });
}); });
it('should render the error page when code exchange fails, and the server responses with html', async () => {
credentialsRepository.findOneBy.mockResolvedValueOnce(credential);
credentialsHelper.getDecrypted.mockResolvedValueOnce({ csrfSecret });
jest.spyOn(Csrf.prototype, 'verify').mockReturnValueOnce(true);
nock('https://example.domain')
.post(
'/token',
'code=code&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A5678%2Frest%2Foauth2-credential%2Fcallback',
)
.reply(403, '<html><body>Code could not be exchanged</body></html>', {
'Content-Type': 'text/html',
});
await controller.handleCallback(req, res);
expect(externalHooks.run).toHaveBeenCalled();
expect(res.render).toHaveBeenCalledWith('oauth-error-callback', {
error: {
message: 'Unsupported content type: text/html',
reason: '"<html><body>Code could not be exchanged</body></html>"',
},
});
});
it('should exchange the code for a valid token, and save it to DB', async () => { it('should exchange the code for a valid token, and save it to DB', async () => {
credentialsRepository.findOneBy.mockResolvedValueOnce(credential); credentialsRepository.findOneBy.mockResolvedValueOnce(credential);
credentialsHelper.getDecrypted.mockResolvedValueOnce({ csrfSecret }); credentialsHelper.getDecrypted.mockResolvedValueOnce({ csrfSecret });