diff --git a/src/auth0-session/client/edge-client.ts b/src/auth0-session/client/edge-client.ts index e4ad5d3be..7c1aca41f 100644 --- a/src/auth0-session/client/edge-client.ts +++ b/src/auth0-session/client/edge-client.ts @@ -9,7 +9,7 @@ import { EndSessionParameters, Telemetry } from './abstract-client'; -import { ApplicationError, DiscoveryError, IdentityProviderError } from '../utils/errors'; +import { ApplicationError, DiscoveryError, IdentityProviderError, UserInfoError } from '../utils/errors'; import { AccessTokenError, AccessTokenErrorCode } from '../../utils/errors'; import urlJoin from 'url-join'; import { Config } from '../config'; @@ -185,7 +185,7 @@ export class EdgeClient extends AbstractClient { return auth0LogoutUrl.toString(); } if (!as.end_session_endpoint) { - throw new Error('no rp inititated logout'); + throw new Error('RP Initiated Logout is not supported on your Authorization Server.'); } const oidcLogoutUrl = new URL(as.end_session_endpoint); Object.entries(parameters).forEach(([key, value]: [string, string]) => { @@ -203,7 +203,11 @@ export class EdgeClient extends AbstractClient { const [as, client] = await this.getClient(); const response = await oauth.userInfoRequest(as, client, accessToken, this.httpOptions()); - return oauth.processUserInfoResponse(as, client, oauth.skipSubjectCheck, response); + try { + return await oauth.processUserInfoResponse(as, client, oauth.skipSubjectCheck, response); + } catch (e) { + throw new UserInfoError(e.message); + } } async refresh(refreshToken: string, extras: { exchangeBody: Record }): Promise { diff --git a/src/auth0-session/client/node-client.ts b/src/auth0-session/client/node-client.ts index 4c692c1d1..264b6a714 100644 --- a/src/auth0-session/client/node-client.ts +++ b/src/auth0-session/client/node-client.ts @@ -16,7 +16,7 @@ import { generators, Issuer } from 'openid-client'; -import { ApplicationError, DiscoveryError, EscapedError, IdentityProviderError } from '../utils/errors'; +import { ApplicationError, DiscoveryError, EscapedError, IdentityProviderError, UserInfoError } from '../utils/errors'; import { createPrivateKey } from 'crypto'; import { exportJWK } from 'jose'; import urlJoin from 'url-join'; @@ -202,7 +202,11 @@ export class NodeClient extends AbstractClient { async userinfo(accessToken: string): Promise> { const client = await this.getClient(); - return client.userinfo(accessToken); + try { + return await client.userinfo(accessToken); + } catch (e) { + throw new UserInfoError(e.message); + } } async refresh(refreshToken: string, extras: { exchangeBody: Record }): Promise { diff --git a/src/auth0-session/utils/errors.ts b/src/auth0-session/utils/errors.ts index 28468a2a1..62c455a53 100644 --- a/src/auth0-session/utils/errors.ts +++ b/src/auth0-session/utils/errors.ts @@ -81,6 +81,14 @@ export class DiscoveryError extends EscapedError { } } +export class UserInfoError extends EscapedError { + constructor(msg: string) { + /* c8 ignore next */ + super(`Userinfo request failing with: ${msg}`); + Object.setPrototypeOf(this, UserInfoError.prototype); + } +} + // eslint-disable-next-line max-len // Basic escaping for putting untrusted data directly into the HTML body, per: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#rule-1-html-encode-before-inserting-untrusted-data-into-html-element-content. export function htmlSafe(input?: string): string | undefined { diff --git a/src/edge.ts b/src/edge.ts index 1c893fd66..02447535d 100644 --- a/src/edge.ts +++ b/src/edge.ts @@ -11,14 +11,14 @@ import { SessionCache, UpdateSession, WithApiAuthRequired, - WithPageAuthRequired + WithPageAuthRequired, + telemetry } from './shared'; import { _initAuth } from './init'; import { setIsUsingNamedExports, setIsUsingOwnInstance } from './utils/instance-check'; import { getConfig, getLoginUrl } from './config'; import { withPageAuthRequiredFactory } from './helpers'; import { EdgeClient } from './auth0-session/client/edge-client'; -import version from './version'; import { WithMiddlewareAuthRequired } from './helpers/with-middleware-auth-required'; const genId = () => { @@ -29,8 +29,6 @@ const genId = () => { .join(''); }; -const telemetry = { name: 'nextjs-auth0', version }; - let instance: Auth0Server & { sessionCache: SessionCache }; /** diff --git a/src/index.ts b/src/index.ts index c08bbd8cc..221ba3022 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,19 +12,17 @@ import { SessionCache, UpdateSession, WithApiAuthRequired, - WithPageAuthRequired + WithPageAuthRequired, + telemetry } from './shared'; import { _initAuth } from './init'; import { setIsUsingNamedExports, setIsUsingOwnInstance } from './utils/instance-check'; import { getConfig, getLoginUrl } from './config'; import { withPageAuthRequiredFactory } from './helpers'; import { NodeClient } from './auth0-session/client/node-client'; -import version from './version'; const genId = () => crypto.randomBytes(16).toString('hex'); -const telemetry = { name: 'nextjs-auth0', version }; - export type Auth0Server = Omit; let instance: Auth0ServerShared & { sessionCache: SessionCache }; diff --git a/src/shared.ts b/src/shared.ts index 03103d7cf..2bf7aa76a 100644 --- a/src/shared.ts +++ b/src/shared.ts @@ -4,6 +4,9 @@ import { SessionCache, GetSession, GetAccessToken, Session, TouchSession, Update import { WithApiAuthRequired, WithPageAuthRequired } from './helpers'; import { ConfigParameters } from './config'; import { WithMiddlewareAuthRequired } from './helpers/with-middleware-auth-required'; +import version from './version'; + +export const telemetry = { name: 'nextjs-auth0', version }; /** * The SDK server instance. diff --git a/src/utils/errors.ts b/src/utils/errors.ts index f7cdefef2..fd2bc53ab 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -108,7 +108,15 @@ export class AccessTokenError extends AuthError { /** * @ignore */ -export type HandlerErrorCause = Error | AuthError | (Error & { status: number; statusCode: number }); +interface HttpError extends Error { + status: number; + statusCode: number; +} + +/** + * @ignore + */ +export type HandlerErrorCause = Error | AuthError | HttpError; type HandlerErrorOptions = { code: string; diff --git a/tests/auth0-session/client/edge-client.test.ts b/tests/auth0-session/client/edge-client.test.ts index 5d4a999e9..48b7805c9 100644 --- a/tests/auth0-session/client/edge-client.test.ts +++ b/tests/auth0-session/client/edge-client.test.ts @@ -13,6 +13,7 @@ import { mockFetch } from '../../fixtures/app-router-helpers'; import { Auth0Request } from '../../../src/auth0-session/http'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { UserInfoError } from '../../../src/auth0-session/utils/errors'; class TestReq extends Auth0Request { constructor() { @@ -283,6 +284,14 @@ describe('edge client', function () { ); }); + it('should throw UserInfoError when userinfo fails', async () => { + nock.cleanAll(); + nock('https://op.example.com').get('/.well-known/openid-configuration').reply(200, wellKnown); + nock('https://op.example.com').get('/userinfo').reply(500, {}); + const client = await getClient(); + await expect(client.userinfo('__test_token__')).rejects.toThrow(UserInfoError); + }); + it('should only support code flow', async () => { await expect(getClient({ authorizationParams: { response_type: 'id_token' } })).rejects.toThrow( 'This SDK only supports `response_type=code` when used in an Edge runtime.' diff --git a/tests/auth0-session/client/node-client.test.ts b/tests/auth0-session/client/node-client.test.ts index 0bbb8bdbd..1aa983d51 100644 --- a/tests/auth0-session/client/node-client.test.ts +++ b/tests/auth0-session/client/node-client.test.ts @@ -5,6 +5,7 @@ import pkg from '../../../package.json'; import wellKnown from '../fixtures/well-known.json'; import version from '../../../src/version'; import { NodeClient } from '../../../src/auth0-session/client/node-client'; +import { UserInfoError } from '../../../src/auth0-session/utils/errors'; const defaultConfig = { secret: '__test_session_secret__', @@ -212,4 +213,11 @@ describe('node client', function () { 'Discovery requests failing for https://op.example.com, expected 200 OK, got: 500 Internal Server Error' ); }); + + it('should throw UserInfoError when userinfo fails', async () => { + nock.cleanAll(); + nock('https://op.example.com').get('/.well-known/openid-configuration').reply(200, wellKnown); + nock('https://op.example.com').get('/userinfo').reply(500, {}); + await expect((await getClient()).userinfo('token')).rejects.toThrow(UserInfoError); + }); }); diff --git a/tests/fixtures/app-router-helpers.ts b/tests/fixtures/app-router-helpers.ts index ef27af18b..ffa3c29c2 100644 --- a/tests/fixtures/app-router-helpers.ts +++ b/tests/fixtures/app-router-helpers.ts @@ -27,9 +27,8 @@ const isEdgeRuntime = export const initAuth0 = (config: ConfigParameters) => { if (isEdgeRuntime) { return edgeInitAuth0(config); - } else { - return nodeInitAuth0(config); } + return nodeInitAuth0(config); }; export const mockFetch = () => { diff --git a/tests/handlers/login-page-router.test.ts b/tests/handlers/login-page-router.test.ts index 712957fc1..340b84ddf 100644 --- a/tests/handlers/login-page-router.test.ts +++ b/tests/handlers/login-page-router.test.ts @@ -8,7 +8,7 @@ import { Cookie, CookieJar } from 'tough-cookie'; describe('login handler (page router)', () => { afterEach(teardown); - test('should create a state', async () => { + test('should create a state, nonce, and code verifier', async () => { const baseUrl = await setup(withoutApi); const cookieJar = new CookieJar(); await get(baseUrl, '/api/auth/login', { cookieJar });