Skip to content

Commit

Permalink
Better error logging of responses for OIDC callback
Browse files Browse the repository at this point in the history
  • Loading branch information
fflorent committed Jul 25, 2024
1 parent bb6445b commit 15732bc
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 7 deletions.
28 changes: 25 additions & 3 deletions app/server/lib/OIDCConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
import * as express from 'express';
import { GristLoginSystem, GristServer } from './GristServer';
import {
Client, ClientMetadata, Issuer, TokenSet, UserinfoResponse
Client, ClientMetadata, Issuer, errors as OIDCError, TokenSet, UserinfoResponse
} from 'openid-client';
import { Sessions } from './Sessions';
import log from 'app/server/lib/log';
Expand Down Expand Up @@ -234,8 +234,9 @@ export class OIDCConfig {
res.redirect(targetUrl ?? '/');
} catch (err) {
log.error(`OIDC callback failed: ${err.stack}`);
if (Object.prototype.hasOwnProperty.call(err, 'response')) {
log.error(`Response received: ${err.response?.body ?? err.response}`);
const maybeResponse = this._maybeExtractDetailsFromError(err);
if (maybeResponse) {
log.error('Response received: %o', maybeResponse);
}

// Delete entirely the session data when the login failed.
Expand Down Expand Up @@ -352,6 +353,27 @@ export class OIDCConfig {

return `${fname} ${lname}`.trim() || userInfo.name;
}

/**
* Returns some response details from either OIDCClient's RPError or OPError,
* which are handy for error logging.
*/
private _maybeExtractDetailsFromError(error: Error) {
if (error instanceof OIDCError.OPError || error instanceof OIDCError.RPError) {
const { response } = error;
if (response) {
// Ensure that we don't log a buffer (which might be noisy), at least for now, unless we're sure that
// would be relevant.
const isBodyPureObject = response.body && Object.getPrototypeOf(response.body) === Object.prototype;
return {
body: isBodyPureObject ? response.body : undefined,
statusCode: response.statusCode,
statusMessage: response.statusMessage,
};
}
}
return null;
}
}

export async function getOIDCLoginSystem(): Promise<GristLoginSystem | undefined> {
Expand Down
13 changes: 9 additions & 4 deletions test/server/lib/OIDCConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Sessions} from "app/server/lib/Sessions";
import log from "app/server/lib/log";
import {assert} from "chai";
import Sinon from "sinon";
import {Client, generators} from "openid-client";
import {Client, generators, errors as OIDCError} from "openid-client";
import express from "express";
import _ from "lodash";
import {RequestWithLogin} from "app/server/lib/Authorizer";
Expand Down Expand Up @@ -672,9 +672,13 @@ describe('OIDCConfig', () => {
session: DEFAULT_SESSION,
} as unknown as express.Request;
clientStub.callbackParams.returns({state: FAKE_STATE});
const errorResponse = {
body: { property: 'response here' },
statusCode: 400,
statusMessage: 'statusMessage'
} as unknown as any;

const err: Error & {response?: {body: string }} = new Error('userinfo failed');
err.response = { body: 'response here' };
const err = new OIDCError.OPError({error: 'userinfo failed'}, errorResponse);
clientStub.userinfo.rejects(err);

await config.handleCallback(
Expand All @@ -685,7 +689,8 @@ describe('OIDCConfig', () => {

assert.equal(logErrorStub.callCount, 2, 'logErrorStub show be called twice');
assert.include(logErrorStub.firstCall.args[0], err.message);
assert.include(logErrorStub.secondCall.args[0], err.response.body);
assert.include(logErrorStub.secondCall.args[0], 'Response received');
assert.deepEqual(logErrorStub.secondCall.args[1], errorResponse);
assert.isTrue(sendAppPageStub.calledOnce, "An error should have been sent");
});
});
Expand Down

0 comments on commit 15732bc

Please sign in to comment.