diff --git a/libs/accounts/errors/src/app-error.ts b/libs/accounts/errors/src/app-error.ts index 0f104eeff26..d2191002411 100644 --- a/libs/accounts/errors/src/app-error.ts +++ b/libs/accounts/errors/src/app-error.ts @@ -10,7 +10,6 @@ import { DEFAULT_ERRROR, IGNORED_ERROR_NUMBERS, DEBUGGABLE_PAYLOAD_KEYS, - OAUTH_ERRNO, } from './constants'; import { OauthError } from './oauth-error'; import type { Request as HapiRequest } from 'hapi'; @@ -1256,15 +1255,6 @@ export class AppError extends Error { }); } - static oauthNotPublicClient() { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: OAUTH_ERRNO.NOT_PUBLIC_CLIENT, - message: 'Not a public client', - }); - } - static redisConflict() { return new AppError({ code: 409, diff --git a/libs/accounts/errors/src/constants.ts b/libs/accounts/errors/src/constants.ts index 10568f2deaa..c9fa42b7aaf 100644 --- a/libs/accounts/errors/src/constants.ts +++ b/libs/accounts/errors/src/constants.ts @@ -160,6 +160,34 @@ export const OAUTH_ERRNO = { DISABLED_CLIENT_ID: 202, }; +export const OAUTH_ERROR_MESSAGES: Record = { + UNKNOWN_CLIENT: 'Unknown client', + INCORRECT_SECRET: 'Incorrect secret', + INCORRECT_REDIRECT: 'Incorrect redirect_uri', + INVALID_ASSERTION: 'Invalid assertion', + UNKNOWN_CODE: 'Unknown code', + INCORRECT_CODE: 'Incorrect code', + EXPIRED_CODE: 'Expired code', + INVALID_TOKEN: 'Invalid token', + INVALID_PARAMETER: 'Invalid request parameter', + INVALID_RESPONSE_TYPE: 'Invalid response_type', + UNAUTHORIZED: 'Unauthorized for route', + FORBIDDEN: 'Forbidden', + INVALID_CONTENT_TYPE: + 'Content-Type must be either application/json or application/x-www-form-urlencoded', + INVALID_SCOPES: 'Requested scopes are not allowed', + EXPIRED_TOKEN: 'Expired token', + NOT_PUBLIC_CLIENT: 'Not a public client', + INCORRECT_CODE_CHALLENGE: 'Incorrect code_challenge', + MISSING_PKCE_PARAMETERS: 'Public clients require PKCE OAuth parameters', + STALE_AUTH_AT: 'Stale authentication timestamp', + MISMATCH_ACR_VALUES: 'Mismatch acr value', + INVALID_GRANT_TYPE: 'Invalid grant_type', + UNKNOWN_TOKEN: 'Unknown token', + SERVER_UNAVAILABLE: 'System unavailable, try again soon', + DISABLED_CLIENT_ID: 'This client has been temporarily disabled', +}; + /** * Takes an object and swaps keys with values. Useful when a value -> key look up is needed. * @param obj - Object to swap keys and values on diff --git a/libs/accounts/errors/src/oauth-error.ts b/libs/accounts/errors/src/oauth-error.ts index 654db973b08..864c8221e1a 100644 --- a/libs/accounts/errors/src/oauth-error.ts +++ b/libs/accounts/errors/src/oauth-error.ts @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { DEFAULT_ERRROR } from './constants'; +import { DEFAULT_ERRROR, OAUTH_ERRNO, OAUTH_ERROR_MESSAGES } from './constants'; const hex = (v: Buffer | string): string => Buffer.isBuffer(v) ? v.toString('hex') : v; @@ -141,8 +141,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 101, - message: 'Unknown client', + errno: OAUTH_ERRNO.UNKNOWN_CLIENT, + message: OAUTH_ERROR_MESSAGES.UNKNOWN_CLIENT, }, { clientId: hex(clientId), @@ -155,8 +155,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 102, - message: 'Incorrect secret', + errno: OAUTH_ERRNO.INCORRECT_SECRET, + message: OAUTH_ERROR_MESSAGES.INCORRECT_SECRET, }, { clientId: hex(clientId), @@ -169,8 +169,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 103, - message: 'Incorrect redirect_uri', + errno: OAUTH_ERRNO.INCORRECT_REDIRECT, + message: OAUTH_ERROR_MESSAGES.INCORRECT_REDIRECT, }, { redirectUri: uri, @@ -182,8 +182,8 @@ export class OauthError extends Error { return new OauthError({ code: 401, error: 'Bad Request', - errno: 104, - message: 'Invalid assertion', + errno: OAUTH_ERRNO.INVALID_ASSERTION, + message: OAUTH_ERROR_MESSAGES.INVALID_ASSERTION, }); } @@ -192,8 +192,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 105, - message: 'Unknown code', + errno: OAUTH_ERRNO.UNKNOWN_CODE, + message: OAUTH_ERROR_MESSAGES.UNKNOWN_CODE, }, { requestCode: code, @@ -206,8 +206,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 106, - message: 'Incorrect code', + errno: OAUTH_ERRNO.INCORRECT_CODE, + message: OAUTH_ERROR_MESSAGES.INCORRECT_CODE, }, { requestCode: hex(code), @@ -221,8 +221,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 107, - message: 'Expired code', + errno: OAUTH_ERRNO.EXPIRED_CODE, + message: OAUTH_ERROR_MESSAGES.EXPIRED_CODE, }, { requestCode: hex(code), @@ -235,8 +235,8 @@ export class OauthError extends Error { return new OauthError({ code: 400, error: 'Bad Request', - errno: 108, - message: 'Invalid token', + errno: OAUTH_ERRNO.INVALID_TOKEN, + message: OAUTH_ERROR_MESSAGES.INVALID_TOKEN, }); } @@ -245,8 +245,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 109, - message: 'Invalid request parameter', + errno: OAUTH_ERRNO.INVALID_PARAMETER, + message: OAUTH_ERROR_MESSAGES.INVALID_PARAMETER, }, { validation: val, @@ -258,8 +258,8 @@ export class OauthError extends Error { return new OauthError({ code: 400, error: 'Bad Request', - errno: 110, - message: 'Invalid response_type', + errno: OAUTH_ERRNO.INVALID_RESPONSE_TYPE, + message: OAUTH_ERROR_MESSAGES.INVALID_RESPONSE_TYPE, }); } @@ -268,8 +268,8 @@ export class OauthError extends Error { { code: 401, error: 'Unauthorized', - errno: 111, - message: 'Unauthorized for route', + errno: OAUTH_ERRNO.UNAUTHORIZED, + message: OAUTH_ERROR_MESSAGES.UNAUTHORIZED, }, { detail: reason, @@ -281,8 +281,8 @@ export class OauthError extends Error { return new OauthError({ code: 403, error: 'Forbidden', - errno: 112, - message: 'Forbidden', + errno: OAUTH_ERRNO.FORBIDDEN, + message: OAUTH_ERROR_MESSAGES.FORBIDDEN, }); } @@ -290,10 +290,8 @@ export class OauthError extends Error { return new OauthError({ code: 415, error: 'Unsupported Media Type', - errno: 113, - message: - 'Content-Type must be either application/json or ' + - 'application/x-www-form-urlencoded', + errno: OAUTH_ERRNO.INVALID_CONTENT_TYPE, + message: OAUTH_ERROR_MESSAGES.INVALID_CONTENT_TYPE, }); } @@ -302,8 +300,8 @@ export class OauthError extends Error { { code: 400, error: 'Invalid scopes', - errno: 114, - message: 'Requested scopes are not allowed', + errno: OAUTH_ERRNO.INVALID_SCOPES, + message: OAUTH_ERROR_MESSAGES.INVALID_SCOPES, }, { invalidScopes: scopes, @@ -316,8 +314,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 115, - message: 'Expired token', + errno: OAUTH_ERRNO.EXPIRED_TOKEN, + message: OAUTH_ERROR_MESSAGES.EXPIRED_TOKEN, }, { expiredAt: expiredAt, @@ -330,8 +328,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 116, - message: 'Not a public client', + errno: OAUTH_ERRNO.NOT_PUBLIC_CLIENT, + message: OAUTH_ERROR_MESSAGES.NOT_PUBLIC_CLIENT, }, { clientId: hex(clientId), @@ -344,8 +342,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 117, - message: 'Incorrect code_challenge', + errno: OAUTH_ERRNO.INCORRECT_CODE_CHALLENGE, + message: OAUTH_ERROR_MESSAGES.INCORRECT_CODE_CHALLENGE, }, { requestCodeChallenge: pkceHashValue, @@ -357,8 +355,8 @@ export class OauthError extends Error { return new OauthError({ code: 400, error: 'PKCE parameters missing', - errno: 118, - message: 'Public clients require PKCE OAuth parameters', + errno: OAUTH_ERRNO.MISSING_PKCE_PARAMETERS, + message: OAUTH_ERROR_MESSAGES.MISSING_PKCE_PARAMETERS, }); } @@ -367,8 +365,8 @@ export class OauthError extends Error { { code: 401, error: 'Bad Request', - errno: 119, - message: 'Stale authentication timestamp', + errno: OAUTH_ERRNO.STALE_AUTH_AT, + message: OAUTH_ERROR_MESSAGES.STALE_AUTH_AT, }, { authAt: authAt, @@ -381,8 +379,8 @@ export class OauthError extends Error { { code: 400, error: 'Bad Request', - errno: 120, - message: 'Mismatch acr value', + errno: OAUTH_ERRNO.MISMATCH_ACR_VALUES, + message: OAUTH_ERROR_MESSAGES.MISMATCH_ACR_VALUES, }, { foundValue } ); @@ -392,8 +390,8 @@ export class OauthError extends Error { return new OauthError({ code: 400, error: 'Bad Request', - errno: 121, - message: 'Invalid grant_type', + errno: OAUTH_ERRNO.INVALID_GRANT_TYPE, + message: OAUTH_ERROR_MESSAGES.INVALID_GRANT_TYPE, }); } @@ -401,8 +399,8 @@ export class OauthError extends Error { return new OauthError({ code: 400, error: 'Bad Request', - errno: 122, - message: 'Unknown token', + errno: OAUTH_ERRNO.UNKNOWN_TOKEN, + message: OAUTH_ERROR_MESSAGES.UNKNOWN_TOKEN, }); } @@ -414,8 +412,8 @@ export class OauthError extends Error { { code: 503, error: 'Client Disabled', - errno: 202, // TODO reconcile this with the auth-server version - message: 'This client has been temporarily disabled', + errno: OAUTH_ERRNO.DISABLED_CLIENT_ID, + message: OAUTH_ERROR_MESSAGES.DISABLED_CLIENT_ID, }, { clientId } ); diff --git a/packages/fxa-auth-server/lib/routes/oauth/introspect.js b/packages/fxa-auth-server/lib/routes/oauth/introspect.js index 19bddc2a4f2..2eb729baaae 100644 --- a/packages/fxa-auth-server/lib/routes/oauth/introspect.js +++ b/packages/fxa-auth-server/lib/routes/oauth/introspect.js @@ -5,7 +5,7 @@ /*jshint camelcase: false*/ const Joi = require('joi'); const validators = require('../../oauth/validators'); -const { AppError } = require('@fxa/accounts/errors'); +const { OauthError } = require('@fxa/accounts/errors'); const { getTokenId } = require('../../oauth/token'); const OAUTH_SERVER_DOCS = require('../../../docs/swagger/oauth-server-api').default; @@ -79,7 +79,7 @@ module.exports = ({ oauthDB }) => ({ // in the future other clients should be able to use it // by providing client_secret in the Authentication header if (!client || !client.publicClient) { - throw AppError.oauthNotPublicClient(); + throw OauthError.notPublicClient(token.clientId); } } } diff --git a/packages/fxa-settings/src/lib/oauth/oauth-errors.ts b/packages/fxa-settings/src/lib/oauth/oauth-errors.ts index da9d9adf5c4..b59cc5412b3 100644 --- a/packages/fxa-settings/src/lib/oauth/oauth-errors.ts +++ b/packages/fxa-settings/src/lib/oauth/oauth-errors.ts @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { interpolate } from '../error-utils'; -import { OAUTH_ERRNO } from '@fxa/accounts/errors'; +import { OAUTH_ERRNO, OAUTH_ERROR_MESSAGES } from '@fxa/accounts/errors'; export type AuthError = { errno: number; @@ -15,97 +15,52 @@ export type AuthError = { export const UNEXPECTED_ERROR = 'Unexpected error'; +const sharedError = ( + key: K, + extra?: Partial +): AuthError => ({ + errno: OAUTH_ERRNO[key], + message: OAUTH_ERROR_MESSAGES[key], + ...extra, +}); + export const OAUTH_ERRORS: Record = { - UNKNOWN_CLIENT: { - errno: OAUTH_ERRNO.UNKNOWN_CLIENT, - message: 'Unknown client', - }, - INCORRECT_REDIRECT: { - errno: OAUTH_ERRNO.INCORRECT_REDIRECT, - message: 'Incorrect redirect_uri', - }, - INVALID_ASSERTION: { - errno: OAUTH_ERRNO.INVALID_ASSERTION, - message: 'Invalid assertion', - }, - UNKNOWN_CODE: { - errno: OAUTH_ERRNO.UNKNOWN_CODE, - message: 'Unknown code', - }, - INCORRECT_CODE: { - errno: OAUTH_ERRNO.INCORRECT_CODE, - message: 'Incorrect code', - }, - EXPIRED_CODE: { - errno: OAUTH_ERRNO.EXPIRED_CODE, - message: 'Expired code', - }, - INVALID_TOKEN: { - errno: OAUTH_ERRNO.INVALID_TOKEN, - message: 'Invalid token', - }, - INVALID_PARAMETER: { - errno: OAUTH_ERRNO.INVALID_PARAMETER, + UNKNOWN_CLIENT: sharedError('UNKNOWN_CLIENT'), + INCORRECT_REDIRECT: sharedError('INCORRECT_REDIRECT'), + INVALID_ASSERTION: sharedError('INVALID_ASSERTION'), + UNKNOWN_CODE: sharedError('UNKNOWN_CODE'), + INCORRECT_CODE: sharedError('INCORRECT_CODE'), + EXPIRED_CODE: sharedError('EXPIRED_CODE'), + INVALID_TOKEN: sharedError('INVALID_TOKEN'), + INVALID_PARAMETER: sharedError('INVALID_PARAMETER', { message: 'Invalid OAuth parameter: %(param)s', interpolate: true, - }, - INVALID_RESPONSE_TYPE: { - errno: OAUTH_ERRNO.INVALID_RESPONSE_TYPE, + }), + INVALID_RESPONSE_TYPE: sharedError('INVALID_RESPONSE_TYPE', { message: UNEXPECTED_ERROR, - }, - UNAUTHORIZED: { - errno: OAUTH_ERRNO.UNAUTHORIZED, - message: 'Unauthorized', - }, - FORBIDDEN: { - errno: OAUTH_ERRNO.FORBIDDEN, - message: 'Forbidden', - }, - INVALID_CONTENT_TYPE: { - errno: OAUTH_ERRNO.INVALID_CONTENT_TYPE, + }), + UNAUTHORIZED: sharedError('UNAUTHORIZED', { message: 'Unauthorized' }), + FORBIDDEN: sharedError('FORBIDDEN'), + INVALID_CONTENT_TYPE: sharedError('INVALID_CONTENT_TYPE', { message: UNEXPECTED_ERROR, - }, - INVALID_SCOPES: { - errno: OAUTH_ERRNO.INVALID_SCOPES, + }), + INVALID_SCOPES: sharedError('INVALID_SCOPES', { message: 'Invalid OAuth parameter: %(param)s', interpolate: true, - }, - EXPIRED_TOKEN: { - errno: OAUTH_ERRNO.EXPIRED_TOKEN, - message: 'Expired token', - }, - NOT_PUBLIC_CLIENT: { - errno: OAUTH_ERRNO.NOT_PUBLIC_CLIENT, - message: 'Not a public client', - }, - INCORRECT_CODE_CHALLENGE: { - errno: OAUTH_ERRNO.INCORRECT_CODE_CHALLENGE, - message: 'Incorrect code_challenge', - }, - MISSING_PKCE_PARAMETERS: { - errno: OAUTH_ERRNO.MISSING_PKCE_PARAMETERS, + }), + EXPIRED_TOKEN: sharedError('EXPIRED_TOKEN'), + NOT_PUBLIC_CLIENT: sharedError('NOT_PUBLIC_CLIENT'), + INCORRECT_CODE_CHALLENGE: sharedError('INCORRECT_CODE_CHALLENGE'), + MISSING_PKCE_PARAMETERS: sharedError('MISSING_PKCE_PARAMETERS', { message: 'PKCE parameters missing', - }, - STALE_AUTHENTICATION_TIMESTAMP: { - errno: OAUTH_ERRNO.STALE_AUTH_AT, - message: 'Stale authentication timestamp', - }, - MISMATCH_ACR_VALUES: { - errno: OAUTH_ERRNO.MISMATCH_ACR_VALUES, + }), + STALE_AUTHENTICATION_TIMESTAMP: sharedError('STALE_AUTH_AT'), + MISMATCH_ACR_VALUES: sharedError('MISMATCH_ACR_VALUES', { message: 'Mismatch acr values', - }, - INVALID_GRANT_TYPE: { - errno: OAUTH_ERRNO.INVALID_GRANT_TYPE, - message: 'Invalid grant_type', - }, - SERVER_UNAVAILABLE: { - errno: OAUTH_ERRNO.SERVER_UNAVAILABLE, - message: 'System unavailable, try again soon', - }, - DISABLED_CLIENT_ID: { - errno: OAUTH_ERRNO.DISABLED_CLIENT_ID, - message: 'System unavailable, try again soon', - }, + }), + INVALID_GRANT_TYPE: sharedError('INVALID_GRANT_TYPE'), + SERVER_UNAVAILABLE: sharedError('SERVER_UNAVAILABLE'), + DISABLED_CLIENT_ID: sharedError('DISABLED_CLIENT_ID'), SERVICE_UNAVAILABLE: { errno: 998, message: 'System unavailable, try again soon',