From 775f5b5e25b1b8ab8c2a8816e47f723393935540 Mon Sep 17 00:00:00 2001 From: Amri Toufali Date: Mon, 1 Dec 2025 20:30:02 -0800 Subject: [PATCH] chore(): delete old AppError/OauthError code --- _scripts/check-frozen.ts | 4 - libs/accounts/errors/src/app-error.ts | 70 +- libs/accounts/errors/src/constants.ts | 28 + libs/accounts/errors/src/index.ts | 2 + .../bin/email_notifications.js | 2 +- packages/fxa-auth-server/bin/key_server.js | 2 +- .../fxa-auth-server/lib/account-delete.ts | 2 +- packages/fxa-auth-server/lib/authMethods.js | 2 +- packages/fxa-auth-server/lib/bounces.js | 2 +- packages/fxa-auth-server/lib/db.ts | 12 +- packages/fxa-auth-server/lib/devices.js | 2 +- .../fxa-auth-server/lib/email-cloud-tasks.ts | 2 +- packages/fxa-auth-server/lib/error.js | 1770 ----------------- .../fxa-auth-server/lib/metrics/events.js | 2 +- .../lib/metrics/glean/index.ts | 4 +- packages/fxa-auth-server/lib/monitoring.js | 2 +- .../fxa-auth-server/lib/oauth/assertion.js | 2 +- .../lib/oauth/auth_client_management.js | 2 +- .../lib/oauth/authorized_clients.js | 2 +- packages/fxa-auth-server/lib/oauth/client.js | 8 +- packages/fxa-auth-server/lib/oauth/error.js | 387 ---- packages/fxa-auth-server/lib/oauth/grant.js | 11 +- .../lib/oauth/jwt_access_token.js | 6 +- .../fxa-auth-server/lib/oauth/jwt_id_token.js | 2 +- packages/fxa-auth-server/lib/oauth/token.js | 2 +- packages/fxa-auth-server/lib/oauth/util.js | 2 +- .../lib/payments/capability.ts | 2 +- .../lib/payments/configuration/manager.ts | 2 +- .../iap/apple-app-store/subscriptions.ts | 6 +- .../payments/iap/google-play/subscriptions.ts | 6 +- .../lib/payments/iap/iap-config.ts | 2 +- .../lib/payments/paypal/helper.ts | 46 +- .../lib/payments/paypal/processor.ts | 2 +- .../fxa-auth-server/lib/payments/stripe.ts | 29 +- packages/fxa-auth-server/lib/pushbox/index.ts | 2 +- .../fxa-auth-server/lib/routes/account.ts | 2 +- .../lib/routes/attached-clients.js | 2 +- .../lib/routes/auth-schemes/auth-oauth.js | 2 +- .../lib/routes/auth-schemes/google-oidc.ts | 2 +- .../lib/routes/auth-schemes/hawk-fxa-token.js | 2 +- .../lib/routes/auth-schemes/mfa.ts | 2 +- .../lib/routes/auth-schemes/refresh-token.js | 13 +- .../lib/routes/auth-schemes/shared-secret.js | 2 +- .../auth-schemes/verified-session-token.js | 2 +- packages/fxa-auth-server/lib/routes/cms.ts | 42 +- .../fxa-auth-server/lib/routes/defaults.js | 2 +- .../lib/routes/devices-and-sessions.js | 10 +- packages/fxa-auth-server/lib/routes/emails.js | 2 +- packages/fxa-auth-server/lib/routes/index.js | 5 + .../lib/routes/linked-accounts.ts | 2 +- packages/fxa-auth-server/lib/routes/mfa.ts | 2 +- .../fxa-auth-server/lib/routes/newsletters.js | 2 +- .../lib/routes/oauth/authorization.js | 4 +- .../lib/routes/oauth/client/get.js | 2 +- .../lib/routes/oauth/destroy.js | 4 +- .../lib/routes/oauth/introspect.js | 4 +- .../lib/routes/oauth/key_data.js | 4 +- .../fxa-auth-server/lib/routes/oauth/token.js | 4 +- .../fxa-auth-server/lib/routes/password.ts | 2 +- .../lib/routes/recovery-codes.js | 2 +- .../lib/routes/recovery-key.js | 5 +- .../lib/routes/recovery-phone.ts | 2 +- .../fxa-auth-server/lib/routes/session.js | 2 +- .../lib/routes/subscriptions/apple.ts | 2 +- .../lib/routes/subscriptions/google.ts | 2 +- .../lib/routes/subscriptions/mozilla.ts | 2 +- .../subscriptions/paypal-notifications.ts | 2 +- .../lib/routes/subscriptions/paypal.ts | 2 +- .../lib/routes/subscriptions/play-pubsub.ts | 2 +- .../routes/subscriptions/stripe-webhook.ts | 10 +- .../lib/routes/subscriptions/stripe.ts | 16 +- .../lib/routes/subscriptions/support.ts | 5 +- .../lib/routes/subscriptions/utils.ts | 2 +- packages/fxa-auth-server/lib/routes/totp.js | 2 +- .../lib/routes/utils/account.ts | 2 +- .../fxa-auth-server/lib/routes/utils/email.js | 2 +- .../fxa-auth-server/lib/routes/utils/otp.ts | 2 +- .../lib/routes/utils/signin.js | 2 +- packages/fxa-auth-server/lib/sentry.js | 2 +- packages/fxa-auth-server/lib/server.js | 3 +- packages/fxa-auth-server/lib/tokens/bundle.js | 2 +- packages/fxa-auth-server/lib/tokens/index.js | 2 +- .../scripts/recorded-future/lib.ts | 2 +- .../scripts/verification-reminders.js | 7 +- .../test/local/account-delete.js | 2 +- .../fxa-auth-server/test/local/authMethods.js | 2 +- .../fxa-auth-server/test/local/bounces.js | 2 +- .../fxa-auth-server/test/local/customs.js | 2 +- .../fxa-auth-server/test/local/devices.js | 2 +- .../test/local/email/bounce.js | 10 +- .../test/local/email/notifications.js | 2 +- packages/fxa-auth-server/test/local/error.js | 18 +- .../test/local/metrics/glean.ts | 10 +- .../local/payments/configuration/manager.js | 2 +- .../test/local/payments/paypal-processor.js | 2 +- .../test/local/payments/paypal.js | 20 +- .../test/local/payments/stripe.js | 11 +- .../fxa-auth-server/test/local/pushbox.js | 2 +- .../test/local/routes/account.js | 18 +- .../test/local/routes/attached-clients.js | 2 +- .../local/routes/auth-schemes/auth-oauth.js | 3 +- .../local/routes/auth-schemes/google-oidc.js | 2 +- .../routes/auth-schemes/hawk-fxa-token.js | 2 +- .../test/local/routes/auth-schemes/mfa.js | 2 +- .../routes/auth-schemes/refresh-token.js | 2 +- .../routes/auth-schemes/shared-secret.js | 2 +- .../auth-schemes/verified-session-token.js | 2 +- .../test/local/routes/devices-and-sessions.js | 2 +- .../test/local/routes/emails.js | 2 +- .../test/local/routes/linked-accounts.js | 60 +- .../fxa-auth-server/test/local/routes/mfa.js | 2 +- .../test/local/routes/newsletters.js | 2 +- .../test/local/routes/oauth.js | 2 +- .../test/local/routes/password.js | 24 +- .../test/local/routes/recovery-codes.js | 2 +- .../test/local/routes/recovery-keys.js | 2 +- .../test/local/routes/recovery-phone.js | 2 +- .../test/local/routes/session.js | 2 +- .../test/local/routes/subscriptions/apple.js | 7 +- .../test/local/routes/subscriptions/google.js | 2 +- .../local/routes/subscriptions/mozilla.js | 2 +- .../subscriptions/paypal-notifications.js | 2 +- .../test/local/routes/subscriptions/paypal.js | 2 +- .../routes/subscriptions/stripe-webhooks.js | 12 +- .../test/local/routes/subscriptions/stripe.js | 47 +- .../test/local/routes/support.js | 2 +- .../fxa-auth-server/test/local/routes/totp.js | 2 +- .../test/local/routes/utils/signin.js | 2 +- .../test/local/senders/emails.ts | 3 +- packages/fxa-auth-server/test/local/sentry.js | 31 +- packages/fxa-auth-server/test/local/server.js | 9 +- packages/fxa-auth-server/test/mocks.js | 2 +- packages/fxa-auth-server/test/oauth/grant.js | 2 +- .../test/oauth/jwt_access_token.js | 2 +- .../test/oauth/jwt_id_token.js | 2 +- .../test/remote/account_destroy_tests.js | 2 +- .../remote/oauth_session_token_scope_tests.js | 255 +-- .../test/remote/oauth_tests.js | 2 +- .../test/remote/recovery_email_emails.js | 2 +- .../test/remote/subscription_tests.js | 2 +- .../test/remote/token_code_tests.js | 448 +++-- .../scripts/delete-unverified-accounts.ts | 2 + .../test/scripts/recorded-future/lib.ts | 2 +- packages/fxa-settings/package.json | 3 +- .../src/lib/auth-errors/auth-errors.ts | 159 +- .../src/lib/gql-key-stretch-upgrade.ts | 5 +- .../src/lib/oauth/oauth-errors.ts | 49 +- .../pages/Signup/ConfirmSignupCode/index.tsx | 5 +- 148 files changed, 958 insertions(+), 2966 deletions(-) delete mode 100644 packages/fxa-auth-server/lib/error.js delete mode 100644 packages/fxa-auth-server/lib/oauth/error.js diff --git a/_scripts/check-frozen.ts b/_scripts/check-frozen.ts index 7d71d1aee1..5b4d9f7a7d 100644 --- a/_scripts/check-frozen.ts +++ b/_scripts/check-frozen.ts @@ -8,10 +8,6 @@ import { execSync } from 'child_process'; // Maintain List of frozen files here! const frozen: Array<{ pattern: string; reason: string }> = [ - { - pattern: 'packages/fxa-auth-server/(lib|lib/oauth)/error.js', - reason: 'Files moved to libs/accounts/errors', - }, { pattern: 'packages/fxa-auth-server/lib/senders/email.js', reason: 'Files moved to libs/accounts/email-sender', diff --git a/libs/accounts/errors/src/app-error.ts b/libs/accounts/errors/src/app-error.ts index f4bfd1628c..0f104eeff2 100644 --- a/libs/accounts/errors/src/app-error.ts +++ b/libs/accounts/errors/src/app-error.ts @@ -10,9 +10,10 @@ import { DEFAULT_ERRROR, IGNORED_ERROR_NUMBERS, DEBUGGABLE_PAYLOAD_KEYS, + OAUTH_ERRNO, } from './constants'; import { OauthError } from './oauth-error'; -import { Request as HapiRequest } from 'hapi'; +import type { Request as HapiRequest } from 'hapi'; /** * Augmented Hapi request. Extends request.app interface auth-server specific feilds that this lib uses. @@ -296,7 +297,7 @@ export class AppError extends Error { ); } - static accountExists(email: string) { + static accountExists(email?: string) { return new AppError( { code: 400, @@ -310,7 +311,7 @@ export class AppError extends Error { ); } - static unknownAccount(email: string) { + static unknownAccount(email?: string) { return new AppError( { code: 400, @@ -387,7 +388,7 @@ export class AppError extends Error { }); } - static invalidVerificationCode(details: Record) { + static invalidVerificationCode(details?: Record) { return new AppError( { code: 400, @@ -408,7 +409,7 @@ export class AppError extends Error { }); } - static invalidRequestParameter(validation: string) { + static invalidRequestParameter(validation?: unknown) { return new AppError( { code: 400, @@ -422,7 +423,7 @@ export class AppError extends Error { ); } - static missingRequestParameter(param: string) { + static missingRequestParameter(param?: string) { return new AppError( { code: 400, @@ -486,7 +487,7 @@ export class AppError extends Error { }); } - static unauthorized(reason: string) { + static unauthorized(reason?: string) { return new AppError( { code: 401, @@ -575,7 +576,7 @@ export class AppError extends Error { ); } - static serviceUnavailable(retryAfter: number) { + static serviceUnavailable(retryAfter?: number) { if (!retryAfter) { retryAfter = 30; } @@ -595,7 +596,7 @@ export class AppError extends Error { ); } - static featureNotEnabled(retryAfter: number) { + static featureNotEnabled(retryAfter?: number) { if (!retryAfter) { retryAfter = 30; } @@ -656,7 +657,7 @@ export class AppError extends Error { }); } - static deviceSessionConflict(deviceId: string) { + static deviceSessionConflict(deviceId?: string) { return new AppError( { code: 400, @@ -770,7 +771,10 @@ export class AppError extends Error { ); } - static currencyCountryMismatch(currency: string, country: string) { + static currencyCountryMismatch( + currency?: string | null, + country?: string | null + ) { return new AppError( { code: 400, @@ -785,7 +789,10 @@ export class AppError extends Error { ); } - static currencyCurrencyMismatch(currencyA: string, currencyB: string) { + static currencyCurrencyMismatch( + currencyA?: string | null, + currencyB?: string | null + ) { return new AppError( { code: 400, @@ -1249,6 +1256,15 @@ 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, @@ -1376,7 +1392,8 @@ export class AppError extends Error { ); } - static unknownCustomer(uid?: string) { + static unknownCustomer(uid?: string | { id?: string | null } | null) { + const resolvedUid = typeof uid === 'string' ? uid : (uid?.id ?? undefined); return new AppError( { code: 404, @@ -1385,7 +1402,7 @@ export class AppError extends Error { message: 'Unknown customer', }, { - uid, + uid: resolvedUid, } ); } @@ -1433,8 +1450,8 @@ export class AppError extends Error { } static rejectedSubscriptionPaymentToken( - message: string, - paymentError: Error + message?: string, + paymentError?: Error ) { return new AppError( { @@ -1443,11 +1460,13 @@ export class AppError extends Error { errno: ERRNO.REJECTED_SUBSCRIPTION_PAYMENT_TOKEN, message, }, + undefined, + undefined, paymentError ); } - static rejectedCustomerUpdate(message: string, paymentError: Error) { + static rejectedCustomerUpdate(message?: string, paymentError?: Error) { return new AppError( { code: 400, @@ -1455,6 +1474,8 @@ export class AppError extends Error { errno: ERRNO.REJECTED_CUSTOMER_UPDATE, message, }, + undefined, + undefined, paymentError ); } @@ -1541,9 +1562,9 @@ export class AppError extends Error { static invalidInvoicePreviewRequest( error: Error, - message: string, - priceId: string, - customer: string + message?: string, + priceId?: string, + customer?: string ) { const extra = error ? [{}, undefined, error] : []; return new AppError( @@ -1638,7 +1659,12 @@ export class AppError extends Error { ); } - static internalValidationError(op: string, data: unknown, error: Error) { + static internalValidationError( + op: string, + data?: unknown, + error?: Error | string + ) { + const errInstance = typeof error === 'string' ? new Error(error) : error; return new AppError( { code: 500, @@ -1651,7 +1677,7 @@ export class AppError extends Error { data, }, {}, - error + errInstance ); } diff --git a/libs/accounts/errors/src/constants.ts b/libs/accounts/errors/src/constants.ts index 42b73a8fea..10568f2dea 100644 --- a/libs/accounts/errors/src/constants.ts +++ b/libs/accounts/errors/src/constants.ts @@ -133,6 +133,33 @@ export const ERRNO = { UNEXPECTED_ERROR: 999, }; +export const OAUTH_ERRNO = { + UNKNOWN_CLIENT: 101, + INCORRECT_SECRET: 102, + INCORRECT_REDIRECT: 103, + INVALID_ASSERTION: 104, + UNKNOWN_CODE: 105, + INCORRECT_CODE: 106, + EXPIRED_CODE: 107, + INVALID_TOKEN: 108, + INVALID_PARAMETER: 109, + INVALID_RESPONSE_TYPE: 110, + UNAUTHORIZED: 111, + FORBIDDEN: 112, + INVALID_CONTENT_TYPE: 113, + INVALID_SCOPES: 114, + EXPIRED_TOKEN: 115, + NOT_PUBLIC_CLIENT: 116, + INCORRECT_CODE_CHALLENGE: 117, + MISSING_PKCE_PARAMETERS: 118, + STALE_AUTH_AT: 119, + MISMATCH_ACR_VALUES: 120, + INVALID_GRANT_TYPE: 121, + UNKNOWN_TOKEN: 122, + SERVER_UNAVAILABLE: 201, + DISABLED_CLIENT_ID: 202, +}; + /** * 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 @@ -149,6 +176,7 @@ function swapObjectKeysAndValues(obj: { [key: string]: string | number }) { * A reversed map of errnos. */ export const ERRNO_REVERSE_MAP = swapObjectKeysAndValues(ERRNO); +export const OAUTH_ERRNO_REVERSE_MAP = swapObjectKeysAndValues(OAUTH_ERRNO); /** * The Default Unexpected Error State diff --git a/libs/accounts/errors/src/index.ts b/libs/accounts/errors/src/index.ts index aaa04cf8d9..81f544514d 100644 --- a/libs/accounts/errors/src/index.ts +++ b/libs/accounts/errors/src/index.ts @@ -2,5 +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/. */ +export * from './constants'; export * from './app-error'; export * from './oauth-error'; +export * from './util'; diff --git a/packages/fxa-auth-server/bin/email_notifications.js b/packages/fxa-auth-server/bin/email_notifications.js index 016d135bbe..b3606df2ed 100644 --- a/packages/fxa-auth-server/bin/email_notifications.js +++ b/packages/fxa-auth-server/bin/email_notifications.js @@ -59,7 +59,7 @@ if (config.subscriptions && config.subscriptions.stripeApiKey) { } } -const error = require('../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const Token = require('../lib/tokens')(log, config); const SQSReceiver = require('../lib/sqs')(log, statsd); const bounces = require('../lib/email/bounces')(log, error, config); diff --git a/packages/fxa-auth-server/bin/key_server.js b/packages/fxa-auth-server/bin/key_server.js index 372686e70a..839f132e64 100755 --- a/packages/fxa-auth-server/bin/key_server.js +++ b/packages/fxa-auth-server/bin/key_server.js @@ -24,7 +24,7 @@ const { } = require('@fxa/shared/cms'); const TracingProvider = require('fxa-shared/tracing/node-tracing'); -const error = require('../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const { JWTool } = require('@fxa/vendored/jwtool'); const { StatsD } = require('hot-shots'); const { Container } = require('typedi'); diff --git a/packages/fxa-auth-server/lib/account-delete.ts b/packages/fxa-auth-server/lib/account-delete.ts index 3b09bf1acd..2bdfc7fef2 100644 --- a/packages/fxa-auth-server/lib/account-delete.ts +++ b/packages/fxa-auth-server/lib/account-delete.ts @@ -13,7 +13,7 @@ import { StatsD } from 'hot-shots'; import * as Sentry from '@sentry/node'; import { ConfigType } from '../config'; -import { ERRNO } from './error'; +import { ERRNO } from '@fxa/accounts/errors'; import OAuthDb from './oauth/db'; import { AppleIAP } from './payments/iap/apple-app-store/apple-iap'; import { PlayBilling } from './payments/iap/google-play/play-billing'; diff --git a/packages/fxa-auth-server/lib/authMethods.js b/packages/fxa-auth-server/lib/authMethods.js index 1f7c1bb2d8..3e74d10aa4 100644 --- a/packages/fxa-auth-server/lib/authMethods.js +++ b/packages/fxa-auth-server/lib/authMethods.js @@ -4,7 +4,7 @@ 'use strict'; -const error = require('./error'); +const { AppError: error } = require('@fxa/accounts/errors'); // Maps our variety of verification methods down to a few short standard // "authentication method reference" strings that we're happy to expose to diff --git a/packages/fxa-auth-server/lib/bounces.js b/packages/fxa-auth-server/lib/bounces.js index ec6e1b4f90..9131acbaf6 100644 --- a/packages/fxa-auth-server/lib/bounces.js +++ b/packages/fxa-auth-server/lib/bounces.js @@ -4,7 +4,7 @@ 'use strict'; -const error = require('./error'); +const { AppError: error } = require('@fxa/accounts/errors'); module.exports = (config, db) => { const configBounces = (config.smtp && config.smtp.bounces) || {}; diff --git a/packages/fxa-auth-server/lib/db.ts b/packages/fxa-auth-server/lib/db.ts index 377ac9e3de..6e34838a43 100644 --- a/packages/fxa-auth-server/lib/db.ts +++ b/packages/fxa-auth-server/lib/db.ts @@ -30,7 +30,7 @@ import { normalizeEmail } from 'fxa-shared/email/helpers'; import { StatsD } from 'hot-shots'; import { Container } from 'typedi'; import random, { base32 } from './crypto/random'; -import error from './error'; +import { AppError as error } from '@fxa/accounts/errors'; import { verificationMethodToString, VerificationMethod, @@ -1064,15 +1064,15 @@ export const createDB = ( ); } - async verifiedLoginSecurityEventsByUid(params: { uid: string; skipTimeframeMs: number}) { + async verifiedLoginSecurityEventsByUid(params: { + uid: string; + skipTimeframeMs: number; + }) { log.trace('DB.verifiedLoginSecurityEventsByUid', { params: params, }); const { uid, skipTimeframeMs } = params; - return SecurityEvent.findByUidAndVerifiedLogin( - uid, - skipTimeframeMs - ); + return SecurityEvent.findByUidAndVerifiedLogin(uid, skipTimeframeMs); } async securityEventsByUid(params: { uid: string }) { diff --git a/packages/fxa-auth-server/lib/devices.js b/packages/fxa-auth-server/lib/devices.js index c4f0092c68..ea1bd48dc2 100644 --- a/packages/fxa-auth-server/lib/devices.js +++ b/packages/fxa-auth-server/lib/devices.js @@ -8,7 +8,7 @@ const isA = require('joi'); const Sentry = require('@sentry/node'); const DESCRIPTION = require('../docs/swagger/shared/descriptions').default; const validators = require('./routes/validators'); -const error = require('./error'); +const { AppError: error } = require('@fxa/accounts/errors'); const oauthDB = require('./oauth/db'); const { DISPLAY_SAFE_UNICODE_WITH_NON_BMP, HEX_STRING, URL_SAFE_BASE_64 } = validators; diff --git a/packages/fxa-auth-server/lib/email-cloud-tasks.ts b/packages/fxa-auth-server/lib/email-cloud-tasks.ts index aa2402e0c4..755a71cb7a 100644 --- a/packages/fxa-auth-server/lib/email-cloud-tasks.ts +++ b/packages/fxa-auth-server/lib/email-cloud-tasks.ts @@ -14,7 +14,7 @@ import { import { ConfigType } from '../config'; import { AuthRequest } from './types'; import { IncomingHttpHeaders } from 'http'; -import AppError from './error'; +import { AppError } from '@fxa/accounts/errors'; import { InactiveAccountsManager, emailTypeToHandlerVals, diff --git a/packages/fxa-auth-server/lib/error.js b/packages/fxa-auth-server/lib/error.js deleted file mode 100644 index 5a6300656a..0000000000 --- a/packages/fxa-auth-server/lib/error.js +++ /dev/null @@ -1,1770 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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/. */ - -'use strict'; - -const inherits = require('util').inherits; -const OauthError = require('./oauth/error'); -const verror = require('verror'); - -const { - AUTH_SERVER_ERRNOS: ERRNO, - AUTH_SERVER_ERRNOS_REVERSE_MAP, -} = require('fxa-shared/lib/errors'); - -const DEFAULTS = { - code: 500, - error: 'Internal Server Error', - errno: ERRNO.UNEXPECTED_ERROR, - message: 'Unspecified error', - info: 'https://mozilla.github.io/ecosystem-platform/api#section/Response-format', -}; - -const TOO_LARGE = - /^Payload (?:content length|size) greater than maximum allowed/; - -const BAD_SIGNATURE_ERRORS = [ - 'Bad mac', - 'Unknown algorithm', - 'Missing required payload hash', - 'Payload is invalid', -]; - -// Payload properties that might help us debug unexpected errors -// when they show up in production. Obviously we don't want to -// accidentally send any sensitive data or PII to a 3rd-party, -// so the set is opt-in rather than opt-out. -const DEBUGGABLE_PAYLOAD_KEYS = new Set([ - 'availableCommands', - 'capabilities', - 'client_id', - 'code', - 'command', - 'duration', - 'excluded', - 'features', - 'messageId', - 'metricsContext', - 'name', - 'preVerified', - 'publicKey', - 'reason', - 'redirectTo', - 'reminder', - 'scope', - 'service', - 'target', - 'to', - 'TTL', - 'ttl', - 'type', - 'unblockCode', - 'verificationMethod', -]); - -function AppError(options, extra, headers, error) { - this.message = options.message || DEFAULTS.message; - this.isBoom = true; - this.stack = options.stack; - if (!this.stack) { - Error.captureStackTrace(this, AppError); - } - if (error) { - // This is where verror stores the error cause passed in. - this.jse_cause = error; - } - this.errno = options.errno || DEFAULTS.errno; - this.output = { - statusCode: options.code || DEFAULTS.code, - payload: { - code: options.code || DEFAULTS.code, - errno: this.errno, - error: options.error || DEFAULTS.error, - message: this.message, - info: options.info || DEFAULTS.info, - }, - headers: headers || {}, - }; - Object.assign(this.output.payload, extra || {}); -} -inherits(AppError, verror.WError); - -AppError.prototype.toString = function () { - return `Error: ${this.message}`; -}; - -AppError.prototype.header = function (name, value) { - this.output.headers[name] = value; -}; - -AppError.prototype.backtrace = function (traced) { - this.output.payload.log = traced; -}; - -/** - Translates an error from Hapi format to our format -*/ -AppError.translate = function (request, response) { - let error; - if (response instanceof AppError) { - return response; - } - if (OauthError.isOauthRoute(request && request.route.path)) { - return OauthError.translate(response); - } else if (response instanceof OauthError) { - return appErrorFromOauthError(response); - } - const payload = response.output.payload; - const reason = response.reason; - if (!payload) { - error = AppError.unexpectedError(request); - } else if ( - payload.statusCode === 500 && - /(socket hang up|ECONNREFUSED)/.test(reason) - ) { - // A connection to a remote service either was not made or timed out. - if (response instanceof Error) { - error = AppError.backendServiceFailure( - undefined, - undefined, - undefined, - response - ); - } else { - error = AppError.backendServiceFailure(); - } - } else if (payload.statusCode === 401) { - // These are common errors generated by Hawk auth lib. - if ( - payload.message === 'Unknown credentials' || - payload.message === 'Invalid credentials' - ) { - error = AppError.invalidToken( - `Invalid authentication token: ${payload.message}` - ); - } else if (payload.message === 'Stale timestamp') { - error = AppError.invalidTimestamp(); - } else if (payload.message === 'Invalid nonce') { - error = AppError.invalidNonce(); - } else if (BAD_SIGNATURE_ERRORS.indexOf(payload.message) !== -1) { - error = AppError.invalidSignature(payload.message); - } else { - error = AppError.invalidToken( - `Invalid authentication token: ${payload.message}` - ); - } - } else if (payload.validation) { - if (payload.message?.includes('is required')) { - error = AppError.missingRequestParameter(payload.validation.keys[0]); - } else { - error = AppError.invalidRequestParameter(payload.validation); - } - } else if (payload.statusCode === 413 && TOO_LARGE.test(payload.message)) { - error = AppError.requestBodyTooLarge(); - } else { - error = new AppError({ - message: payload.message, - code: payload.statusCode, - error: payload.error, - errno: payload.errno, - info: payload.info, - stack: response.stack, - }); - - if (response.data) { - error.output.payload.data = JSON.stringify(response.data); - } - - if (payload.statusCode >= 500) { - decorateErrorWithRequest(error, request); - } - } - return error; -}; - -AppError.mapErrnoToKey = function (error) { - if (error && error.errno && AUTH_SERVER_ERRNOS_REVERSE_MAP[error.errno]) { - return AUTH_SERVER_ERRNOS_REVERSE_MAP[error.errno]; - } -}; - -// Helper functions for creating particular response types. - -AppError.dbIncorrectPatchLevel = function (level, levelRequired) { - return new AppError( - { - code: 400, - error: 'Server Startup', - errno: ERRNO.SERVER_CONFIG_ERROR, - message: 'Incorrect Database Patch Level', - }, - { - level: level, - levelRequired: levelRequired, - } - ); -}; - -AppError.accountExists = function (email) { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.ACCOUNT_EXISTS, - message: 'Account already exists', - }, - { - email: email, - } - ); -}; - -AppError.unknownAccount = function (email) { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.ACCOUNT_UNKNOWN, - message: 'Unknown account', - }, - { - email: email, - } - ); -}; - -AppError.incorrectPassword = function (dbEmail, requestEmail) { - if (dbEmail !== requestEmail) { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INCORRECT_EMAIL_CASE, - message: 'Incorrect email case', - }, - { - email: dbEmail, - } - ); - } - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INCORRECT_PASSWORD, - message: 'Incorrect password', - }, - { - email: dbEmail, - } - ); -}; - -AppError.cannotCreatePassword = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.CANNOT_CREATE_PASSWORD, - message: 'Can not create password, password already set.', - }); -}; - -AppError.cannotLoginNoPasswordSet = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.UNABLE_TO_LOGIN_NO_PASSWORD_SET, - message: 'Complete account setup, please reset password to continue.', - }); -}; - -AppError.unverifiedAccount = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.ACCOUNT_UNVERIFIED, - message: 'Unconfirmed account', - }); -}; - -AppError.insufficientAal = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.INSUFFICIENT_AAL, - message: 'Insufficient AAL', - }); -}; - -AppError.invalidVerificationCode = function (details) { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_VERIFICATION_CODE, - message: 'Invalid confirmation code', - }, - details - ); -}; - -AppError.invalidRequestBody = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_JSON, - message: 'Invalid JSON in request body', - }); -}; - -AppError.invalidRequestParameter = function (validation) { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_PARAMETER, - message: 'Invalid parameter in request body', - }, - { - validation: validation, - } - ); -}; - -AppError.missingRequestParameter = function (param) { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.MISSING_PARAMETER, - message: `Missing parameter in request body${param ? `: ${param}` : ''}`, - }, - { - param: param, - } - ); -}; - -AppError.invalidSignature = function (message) { - return new AppError({ - code: 401, - error: 'Unauthorized', - errno: ERRNO.INVALID_REQUEST_SIGNATURE, - message: message || 'Invalid request signature', - }); -}; - -AppError.invalidToken = function (message) { - return new AppError({ - code: 401, - error: 'Unauthorized', - errno: ERRNO.INVALID_TOKEN, - message: message || 'Invalid authentication token in request signature', - }); -}; - -AppError.invalidMfaToken = function () { - return new AppError({ - code: 401, - error: 'Unauthorized', - errno: ERRNO.INVALID_MFA_TOKEN, - message: 'Invalid or expired MFA token', - }); -}; - -AppError.invalidTimestamp = function () { - return new AppError( - { - code: 401, - error: 'Unauthorized', - errno: ERRNO.INVALID_TIMESTAMP, - message: 'Invalid timestamp in request signature', - }, - { - serverTime: Math.floor(+new Date() / 1000), - } - ); -}; - -AppError.invalidNonce = function () { - return new AppError({ - code: 401, - error: 'Unauthorized', - errno: ERRNO.INVALID_NONCE, - message: 'Invalid nonce in request signature', - }); -}; - -AppError.unauthorized = function unauthorized(reason) { - return new AppError( - { - code: 401, - error: 'Unauthorized', - errno: ERRNO.INVALID_TOKEN, - message: 'Unauthorized for route', - }, - { - detail: reason, - } - ); -}; - -AppError.missingContentLength = function () { - return new AppError({ - code: 411, - error: 'Length Required', - errno: ERRNO.MISSING_CONTENT_LENGTH_HEADER, - message: 'Missing content-length header', - }); -}; - -AppError.requestBodyTooLarge = function () { - return new AppError({ - code: 413, - error: 'Request Entity Too Large', - errno: ERRNO.REQUEST_TOO_LARGE, - message: 'Request body too large', - }); -}; - -AppError.tooManyRequests = function ( - retryAfter, - retryAfterLocalized, - canUnblock -) { - if (!retryAfter) { - retryAfter = 30; - } - - const extraData = { - retryAfter: retryAfter, - }; - - if (retryAfterLocalized) { - extraData.retryAfterLocalized = retryAfterLocalized; - } - - if (canUnblock) { - extraData.verificationMethod = 'email-captcha'; - extraData.verificationReason = 'login'; - } - - const error = new AppError( - { - code: 429, - error: 'Too Many Requests', - errno: ERRNO.THROTTLED, - message: 'Client has sent too many requests', - }, - extraData, - { - 'retry-after': retryAfter, - } - ); - - return error; -}; - -AppError.requestBlocked = function (canUnblock) { - let extra; - if (canUnblock) { - extra = { - verificationMethod: 'email-captcha', - verificationReason: 'login', - }; - } - return new AppError( - { - code: 400, - error: 'Request blocked', - errno: ERRNO.REQUEST_BLOCKED, - message: 'The request was blocked for security reasons', - }, - extra - ); -}; - -AppError.serviceUnavailable = function (retryAfter) { - if (!retryAfter) { - retryAfter = 30; - } - return new AppError( - { - code: 503, - error: 'Service Unavailable', - errno: ERRNO.SERVER_BUSY, - message: 'Service unavailable', - }, - { - retryAfter: retryAfter, - }, - { - 'retry-after': retryAfter, - } - ); -}; - -AppError.featureNotEnabled = function (retryAfter) { - if (!retryAfter) { - retryAfter = 30; - } - return new AppError( - { - code: 503, - error: 'Feature not enabled', - errno: ERRNO.FEATURE_NOT_ENABLED, - message: 'Feature not enabled', - }, - { - retryAfter: retryAfter, - }, - { - 'retry-after': retryAfter, - } - ); -}; - -AppError.gone = function () { - return new AppError({ - code: 410, - error: 'Gone', - errno: ERRNO.ENDPOINT_NOT_SUPPORTED, - message: 'This endpoint is no longer supported', - }); -}; - -AppError.goneFourOhFour = function () { - return new AppError({ - code: 404, - error: 'Gone', - errno: ERRNO.ENDPOINT_NOT_SUPPORTED, - message: 'This endpoint is no longer supported', - }); -}; - -AppError.mustResetAccount = function (email) { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.ACCOUNT_RESET, - message: 'Account must be reset', - }, - { - email: email, - } - ); -}; - -AppError.unknownDevice = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.DEVICE_UNKNOWN, - message: 'Unknown device', - }); -}; - -AppError.deviceSessionConflict = function (deviceId) { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.DEVICE_CONFLICT, - message: 'Session already registered by another device', - }, - { deviceId } - ); -}; - -AppError.invalidUnblockCode = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_UNBLOCK_CODE, - message: 'Invalid unblock code', - }); -}; - -AppError.invalidPhoneNumber = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_PHONE_NUMBER, - message: 'Invalid phone number', - }); -}; - -AppError.recoveryCodesAlreadyExist = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RECOVERY_CODES_ALREADY_EXISTS, - message: 'Recovery codes or a verified TOTP token already exist', - }); -}; - -AppError.recoveryPhoneNumberAlreadyExists = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RECOVERY_PHONE_NUMBER_ALREADY_EXISTS, - message: 'Recovery phone number already exists', - }); -}; - -AppError.recoveryPhoneNumberDoesNotExist = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RECOVERY_PHONE_NUMBER_DOES_NOT_EXIST, - message: 'Recovery phone number does not exist', - }); -}; - -AppError.recoveryPhoneRemoveMissingRecoveryCodes = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RECOVERY_PHONE_REMOVE_MISSING_RECOVERY_CODES, - message: - 'Unable to remove recovery phone, missing backup authentication codes.', - }); -}; - -AppError.smsSendRateLimitExceeded = () => { - return new AppError({ - code: 429, - error: 'Too many requests', - errno: ERRNO.SMS_SEND_RATE_LIMIT_EXCEEDED, - message: 'Text message limit reached', - }); -}; - -AppError.recoveryPhoneRegistrationLimitReached = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RECOVERY_PHONE_REGISTRATION_LIMIT_REACHED, - message: - 'Limit reached for number off accounts that can be associated with phone number.', - }); -}; - -AppError.invalidRegion = (region) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_REGION, - message: 'Invalid region', - }, - { - region, - } - ); -}; - -AppError.unsupportedLocation = (country) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.UNSUPPORTED_LOCATION, - message: 'Location is not supported according to our Terms of Service.', - }, - { - country, - } - ); -}; - -AppError.currencyCountryMismatch = (currency, country) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_REGION, - message: 'Funding source country does not match plan currency.', - }, - { - currency, - country, - } - ); -}; - -AppError.currencyCurrencyMismatch = (currencyA, currencyB) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_CURRENCY, - message: `Changing currencies is not permitted.`, - }, - { - currencyA, - currencyB, - } - ); -}; - -AppError.billingAgreementExists = (customerId) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.BILLING_AGREEMENT_EXISTS, - message: `Billing agreement already on file for this customer.`, - }, - { - customerId, - } - ); -}; - -AppError.missingPaypalPaymentToken = (customerId) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.MISSING_PAYPAL_PAYMENT_TOKEN, - message: `PayPal payment token is missing.`, - }, - { - customerId, - } - ); -}; - -AppError.missingPaypalBillingAgreement = (customerId) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.MISSING_PAYPAL_BILLING_AGREEMENT, - message: `PayPal billing agreement is missing for the existing subscriber.`, - }, - { - customerId, - } - ); -}; - -AppError.invalidMessageId = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_MESSAGE_ID, - message: 'Invalid message id', - }); -}; - -AppError.messageRejected = (reason, reasonCode) => { - return new AppError( - { - code: 500, - error: 'Internal Server Error', - errno: ERRNO.MESSAGE_REJECTED, - message: 'Message rejected', - }, - { - reason, - reasonCode, - } - ); -}; - -AppError.emailComplaint = (bouncedAt) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.BOUNCE_COMPLAINT, - message: 'Email account sent complaint', - }, - { - bouncedAt, - } - ); -}; - -AppError.emailBouncedHard = (bouncedAt) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.BOUNCE_HARD, - message: 'Email account hard bounced', - }, - { - bouncedAt, - } - ); -}; - -AppError.emailBouncedSoft = (bouncedAt) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.BOUNCE_SOFT, - message: 'Email account soft bounced', - }, - { - bouncedAt, - } - ); -}; - -AppError.emailExists = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.EMAIL_EXISTS, - message: 'Email already exists', - }); -}; - -AppError.cannotDeletePrimaryEmail = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.EMAIL_DELETE_PRIMARY, - message: 'Can not delete primary email', - }); -}; - -AppError.unverifiedSession = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.SESSION_UNVERIFIED, - message: 'Unconfirmed session', - }); -}; - -AppError.yourPrimaryEmailExists = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.USER_PRIMARY_EMAIL_EXISTS, - message: 'Can not add secondary email that is same as your primary', - }); -}; - -AppError.verifiedPrimaryEmailAlreadyExists = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.VERIFIED_PRIMARY_EMAIL_EXISTS, - message: 'Email already exists', - }); -}; - -AppError.verifiedSecondaryEmailAlreadyExists = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.VERIFIED_SECONDARY_EMAIL_EXISTS, - message: 'Email already exists', - }); -}; - -// This error is thrown when someone attempts to add a secondary email -// that is the same as the primary email of another account, but the account -// was recently created ( < 24hrs). -AppError.unverifiedPrimaryEmailNewlyCreated = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.UNVERIFIED_PRIMARY_EMAIL_NEWLY_CREATED, - message: 'Email already exists', - }); -}; - -AppError.unverifiedPrimaryEmailHasActiveSubscription = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.UNVERIFIED_PRIMARY_EMAIL_HAS_ACTIVE_SUBSCRIPTION, - message: 'Account for this email has an active subscription', - }); -}; - -AppError.maxSecondaryEmailsReached = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.MAX_SECONDARY_EMAILS_REACHED, - message: 'You have reached the maximum allowed secondary emails', - }); -}; - -AppError.alreadyOwnsEmail = () => { - return new AppError({ - code: 400, - error: 'Conflict', - errno: ERRNO.ACCOUNT_OWNS_EMAIL, - message: 'This email already exists on your account', - }); -}; - -AppError.cannotLoginWithSecondaryEmail = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.LOGIN_WITH_SECONDARY_EMAIL, - message: 'Sign in with this email type is not currently supported', - }); -}; - -AppError.unknownSecondaryEmail = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.SECONDARY_EMAIL_UNKNOWN, - message: 'Unknown email', - }); -}; - -AppError.cannotResetPasswordWithSecondaryEmail = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RESET_PASSWORD_WITH_SECONDARY_EMAIL, - message: 'Reset password with this email type is not currently supported', - }); -}; - -AppError.invalidSigninCode = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_SIGNIN_CODE, - message: 'Invalid signin code', - }); -}; - -AppError.cannotChangeEmailToUnverifiedEmail = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.CHANGE_EMAIL_TO_UNVERIFIED_EMAIL, - message: 'Can not change primary email to an unconfirmed email', - }); -}; - -AppError.cannotChangeEmailToUnownedEmail = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.CHANGE_EMAIL_TO_UNOWNED_EMAIL, - message: - 'Can not change primary email to an email that does not belong to this account', - }); -}; - -AppError.cannotLoginWithEmail = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.LOGIN_WITH_INVALID_EMAIL, - message: 'This email can not currently be used to login', - }); -}; - -AppError.cannotResendEmailCodeToUnownedEmail = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RESEND_EMAIL_CODE_TO_UNOWNED_EMAIL, - message: - 'Can not resend email code to an email that does not belong to this account', - }); -}; - -AppError.cannotSendEmail = function (isNewAddress) { - if (!isNewAddress) { - return new AppError({ - code: 500, - error: 'Internal Server Error', - errno: ERRNO.FAILED_TO_SEND_EMAIL, - message: 'Failed to send email', - }); - } - return new AppError({ - code: 422, - error: 'Unprocessable Entity', - errno: ERRNO.FAILED_TO_SEND_EMAIL, - message: 'Failed to send email', - }); -}; - -AppError.invalidTokenVerficationCode = function (details) { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_TOKEN_VERIFICATION_CODE, - message: 'Invalid token confirmation code', - }, - details - ); -}; - -AppError.expiredTokenVerficationCode = function (details) { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.EXPIRED_TOKEN_VERIFICATION_CODE, - message: 'Expired token confirmation code', - }, - details - ); -}; - -AppError.totpTokenAlreadyExists = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.TOTP_TOKEN_EXISTS, - message: 'TOTP token already exists for this account.', - }); -}; - -AppError.totpTokenDoesNotExist = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.TOTP_SECRET_DOES_NOT_EXIST, - message: 'TOTP secret does not exist for this account.', - }); -}; - -AppError.totpTokenNotFound = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.TOTP_TOKEN_NOT_FOUND, - message: 'TOTP token not found.', - }); -}; - -AppError.recoveryCodeNotFound = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RECOVERY_CODE_NOT_FOUND, - message: 'Backup authentication code not found.', - }); -}; - -AppError.unavailableDeviceCommand = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.DEVICE_COMMAND_UNAVAILABLE, - message: 'Unavailable device command.', - }); -}; - -AppError.recoveryKeyNotFound = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RECOVERY_KEY_NOT_FOUND, - message: 'Account recovery key not found.', - }); -}; - -AppError.recoveryKeyInvalid = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RECOVERY_KEY_INVALID, - message: 'Account recovery key is not valid.', - }); -}; - -AppError.totpRequired = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.TOTP_REQUIRED, - message: - 'This request requires two step authentication enabled on your account.', - }); -}; - -AppError.recoveryKeyExists = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.RECOVERY_KEY_EXISTS, - message: 'Account recovery key already exists.', - }); -}; - -AppError.unknownClientId = (clientId) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.UNKNOWN_CLIENT_ID, - message: 'Unknown client_id', - }, - { - clientId, - } - ); -}; - -AppError.incorrectClientSecret = (clientId) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INCORRECT_CLIENT_SECRET, - message: 'Incorrect client_secret', - }, - { - clientId, - } - ); -}; - -AppError.staleAuthAt = (authAt) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.STALE_AUTH_AT, - message: 'Stale auth timestamp', - }, - { - authAt, - } - ); -}; - -AppError.notPublicClient = function notPublicClient() { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.NOT_PUBLIC_CLIENT, - message: 'Not a public client', - }); -}; - -AppError.redisConflict = () => { - return new AppError({ - code: 409, - error: 'Conflict', - errno: ERRNO.REDIS_CONFLICT, - message: 'Redis WATCH detected a conflicting update', - }); -}; - -AppError.incorrectRedirectURI = (redirectUri) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INCORRECT_REDIRECT_URI, - message: 'Incorrect redirect URI', - }, - { - redirectUri, - } - ); -}; - -AppError.unknownAuthorizationCode = (code) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.UNKNOWN_AUTHORIZATION_CODE, - message: 'Unknown authorization code', - }, - { - code, - } - ); -}; - -AppError.mismatchAuthorizationCode = (code, clientId) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.MISMATCH_AUTHORIZATION_CODE, - message: 'Mismatched authorization code', - }, - { - code, - clientId, - } - ); -}; - -AppError.expiredAuthorizationCode = (code, expiredAt) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.EXPIRED_AUTHORIZATION_CODE, - message: 'Expired authorization code', - }, - { - code, - expiredAt, - } - ); -}; - -AppError.invalidResponseType = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_RESPONSE_TYPE, - message: 'Invalid response_type', - }); -}; - -AppError.invalidScopes = (invalidScopes) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_SCOPES, - message: 'Requested scopes are not allowed', - }, - { - invalidScopes, - } - ); -}; - -AppError.missingPkceParameters = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.MISSING_PKCE_PARAMETERS, - message: 'Public clients require PKCE OAuth parameters', - }); -}; - -AppError.invalidPkceChallenge = (pkceHashValue) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_PKCE_CHALLENGE, - message: 'Public clients require PKCE OAuth parameters', - }, - { - pkceHashValue, - } - ); -}; - -AppError.invalidPromoCode = (promotionCode) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_PROMOTION_CODE, - message: 'Invalid promotion code', - }, - { - promotionCode, - } - ); -}; - -AppError.unknownCustomer = (uid) => { - return new AppError( - { - code: 404, - error: 'Not Found', - errno: ERRNO.UNKNOWN_SUBSCRIPTION_CUSTOMER, - message: 'Unknown customer', - }, - { - uid, - } - ); -}; - -AppError.unknownSubscription = (subscriptionId) => { - return new AppError( - { - code: 404, - error: 'Not Found', - errno: ERRNO.UNKNOWN_SUBSCRIPTION, - message: 'Unknown subscription', - }, - { - subscriptionId, - } - ); -}; - -AppError.unknownAppName = (appName) => { - return new AppError( - { - code: 404, - error: 'Not Found', - errno: ERRNO.IAP_UNKNOWN_APPNAME, - message: 'Unknown app name', - }, - { - appName, - } - ); -}; - -AppError.unknownSubscriptionPlan = (planId) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.UNKNOWN_SUBSCRIPTION_PLAN, - message: 'Unknown subscription plan', - }, - { - planId, - } - ); -}; - -AppError.rejectedSubscriptionPaymentToken = (message, paymentError) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.REJECTED_SUBSCRIPTION_PAYMENT_TOKEN, - message, - }, - paymentError - ); -}; - -AppError.rejectedCustomerUpdate = (message, paymentError) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.REJECTED_CUSTOMER_UPDATE, - message, - }, - paymentError - ); -}; - -AppError.subscriptionAlreadyCancelled = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.SUBSCRIPTION_ALREADY_CANCELLED, - message: 'Subscription has already been cancelled', - }); -}; - -AppError.invalidPlanUpdate = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_PLAN_UPDATE, - message: 'Subscription plan is not a valid update', - }); -}; - -AppError.paymentFailed = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.PAYMENT_FAILED, - message: 'Payment method failed', - }); -}; - -AppError.subscriptionAlreadyChanged = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.SUBSCRIPTION_ALREADY_CHANGED, - message: 'Subscription has already been cancelled', - }); -}; - -AppError.subscriptionAlreadyExists = () => { - return new AppError({ - code: 409, - error: 'Already subscribed', - errno: ERRNO.SUBSCRIPTION_ALREADY_EXISTS, - message: 'User already subscribed.', - }); -}; - -AppError.userAlreadySubscribedToProduct = () => { - return new AppError({ - code: 409, - error: 'Already subscribed to product with different plan', - errno: ERRNO.SUBSCRIPTION_ALREADY_EXISTS, - message: 'User already subscribed to product with different plan.', - }); -}; - -AppError.iapInvalidToken = (error) => { - const extra = error ? [{}, undefined, error] : []; - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.IAP_INVALID_TOKEN, - message: `Invalid IAP token${error?.message ? `: ${error.message}` : ''}`, - }, - ...extra - ); -}; - -AppError.iapPurchaseConflict = (error) => { - const extra = error ? [{}, undefined, error] : []; - return new AppError( - { - code: 403, - error: 'Forbidden', - errno: ERRNO.IAP_PURCHASE_ALREADY_REGISTERED, - message: 'Purchase has been registered to another user.', - }, - ...extra - ); -}; - -AppError.invalidInvoicePreviewRequest = (error, message, priceId, customer) => { - const extra = error ? [{}, undefined, error] : []; - return new AppError( - { - code: 500, - error: 'Internal Server Error', - errno: ERRNO.INVALID_INVOICE_PREVIEW_REQUEST, - message, - }, - { - priceId, - customer, - }, - ...extra - ); -}; - -AppError.iapInternalError = (error) => { - const extra = error ? [{}, undefined, error] : []; - return new AppError( - { - code: 500, - error: 'Internal Server Error', - errno: ERRNO.IAP_INTERNAL_OTHER, - message: 'IAP Internal Error', - }, - ...extra - ); -}; - -AppError.insufficientACRValues = (foundValue) => { - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.INSUFFICIENT_ACR_VALUES, - message: - 'Required Authentication Context Reference values could not be satisfied', - }, - { - foundValue, - } - ); -}; - -AppError.unknownRefreshToken = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.REFRESH_TOKEN_UNKNOWN, - message: 'Unknown refresh token', - }); -}; - -AppError.invalidOrExpiredOtpCode = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_EXPIRED_OTP_CODE, - message: 'Invalid or expired confirmation code', - }); -}; - -AppError.thirdPartyAccountError = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.THIRD_PARTY_ACCOUNT_ERROR, - message: 'Could not login with third party account, please try again later', - }); -}; - -AppError.backendServiceFailure = (service, operation, extra, error) => { - if (extra) { - return new AppError( - { - code: 500, - error: 'Internal Server Error', - errno: ERRNO.BACKEND_SERVICE_FAILURE, - message: 'System unavailable, try again soon', - }, - { - service, - operation, - ...extra, - }, - {}, - error - ); - } - return new AppError( - { - code: 500, - error: 'Internal Server Error', - errno: ERRNO.BACKEND_SERVICE_FAILURE, - message: 'System unavailable, try again soon', - }, - { - service, - operation, - }, - {}, - error - ); -}; - -AppError.disabledClientId = (clientId, retryAfter) => { - if (!retryAfter) { - retryAfter = 30; - } - return new AppError( - { - code: 503, - error: 'Client Disabled', - errno: ERRNO.DISABLED_CLIENT_ID, - message: 'This client has been temporarily disabled', - }, - { - clientId, - retryAfter, - }, - { - 'retry-after': retryAfter, - } - ); -}; - -AppError.internalValidationError = (op, data, error) => { - return new AppError( - { - code: 500, - error: 'Internal Server Error', - errno: ERRNO.INTERNAL_VALIDATION_ERROR, - message: 'An internal validation check failed.', - }, - { - op, - data, - }, - {}, - error - ); -}; - -AppError.unexpectedError = (request) => { - const error = new AppError({}); - decorateErrorWithRequest(error, request); - return error; -}; - -AppError.missingSubscriptionForSourceError = (op, data) => - new AppError( - { - code: 500, - error: 'Missing subscription for source', - errno: ERRNO.UNKNOWN_SUBSCRIPTION_FOR_SOURCE, - message: 'Failed to find a subscription associated with Stripe source.', - }, - { - op, - data, - } - ); - -AppError.accountCreationRejected = () => { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.ACCOUNT_CREATION_REJECTED, - message: 'Account creation rejected.', - }); -}; - -AppError.subscriptionPromotionCodeNotApplied = (error, message) => { - const extra = error ? [{}, undefined, error] : []; - return new AppError( - { - code: 400, - error: 'Bad Request', - errno: ERRNO.SUBSCRIPTION_PROMO_CODE_NOT_APPLIED, - message, - }, - ...extra - ); -}; - -AppError.invalidCloudTaskEmailType = function () { - return new AppError({ - code: 400, - error: 'Bad Request', - errno: ERRNO.INVALID_CLOUDTASK_EMAILTYPE, - message: 'Invalid email type', - }); -}; - -function decorateErrorWithRequest(error, request) { - if (request) { - error.output.payload.request = { - // request.app.devices and request.app.metricsContext are async, so can't be included here - acceptLanguage: request.app.acceptLanguage, - locale: request.app.locale, - userAgent: request.app.ua, - method: request.method, - path: request.path, - query: request.query, - payload: scrubPii(request.payload), - headers: scrubHeaders(request.headers), - }; - } -} - -function scrubPii(payload) { - if (!payload) { - return; - } - - return Object.entries(payload).reduce((scrubbed, [key, value]) => { - if (DEBUGGABLE_PAYLOAD_KEYS.has(key)) { - scrubbed[key] = value; - } - - return scrubbed; - }, {}); -} - -function scrubHeaders(headers) { - const scrubbed = { ...headers }; - delete scrubbed['x-forwarded-for']; - return scrubbed; -} - -function appErrorFromOauthError(err) { - switch (err.errno) { - case 101: - return AppError.unknownClientId(err.clientId); - case 102: - return AppError.incorrectClientSecret(err.clientId); - case 103: - return AppError.incorrectRedirectURI(err.redirectUri); - case 104: - return AppError.invalidToken(); - case 105: - return AppError.unknownAuthorizationCode(err.code); - case 106: - return AppError.mismatchAuthorizationCode(err.code, err.clientId); - case 107: - return AppError.expiredAuthorizationCode(err.code, err.expiredAt); - case 108: - return AppError.invalidToken(); - case 109: - return AppError.invalidRequestParameter(err.validation); - case 110: - return AppError.invalidResponseType(); - case 114: - return AppError.invalidScopes(err.invalidScopes); - case 116: - return AppError.notPublicClient(); - case 117: - return AppError.invalidPkceChallenge(err.pkceHashValue); - case 118: - return AppError.missingPkceParameters(); - case 119: - return AppError.staleAuthAt(err.authAt); - case 120: - return AppError.insufficientACRValues(err.foundValue); - case 121: - return AppError.invalidRequestParameter('grant_type'); - case 122: - return AppError.unknownRefreshToken(); - case 201: - return AppError.serviceUnavailable(err.retryAfter); - case 202: - return AppError.disabledClientId(err.clientId); - default: - return err; - } -} - -// Maintain list of errors that should not be sent to Sentry -const IGNORED_ERROR_NUMBERS = [ - ERRNO.BOUNCE_HARD, - ERRNO.BOUNCE_SOFT, - ERRNO.BOUNCE_COMPLAINT, -]; - -/** - * Prevents errors from being captured in sentry. - * - * @param {Error} error An error with an error number. Note that errors of type vError will - * use the underlying jse_cause error if possible. - */ -function ignoreErrors(error) { - if (!error) { - return; - } - - // Prefer jse_cause, but fallback to top level error if needed - const statusCode = - determineStatusCode(error.jse_cause) || determineStatusCode(error); - - const errno = error.jse_cause?.errno || error.errno; - - // Ignore non 500 status codes and specific error numbers - return statusCode < 500 || IGNORED_ERROR_NUMBERS.includes(errno); -} - -/** - * Given an error tries to determine the HTTP status code associated with the error. - * @param {*} error - * @returns - */ -function determineStatusCode(error) { - if (!error) { - return; - } - - return error.statusCode || error.output?.statusCode || error.code; -} - -module.exports = AppError; -module.exports.ERRNO = ERRNO; -module.exports.ignoreErrors = ignoreErrors; diff --git a/packages/fxa-auth-server/lib/metrics/events.js b/packages/fxa-auth-server/lib/metrics/events.js index c03693e7a4..1a052f2704 100644 --- a/packages/fxa-auth-server/lib/metrics/events.js +++ b/packages/fxa-auth-server/lib/metrics/events.js @@ -4,7 +4,7 @@ 'use strict'; -const error = require('../error'); +const { AppError: error } = require('@fxa/accounts/errors'); const ACTIVITY_EVENTS = new Set([ 'account.changedPassword', diff --git a/packages/fxa-auth-server/lib/metrics/glean/index.ts b/packages/fxa-auth-server/lib/metrics/glean/index.ts index e2bf115762..b7e3e43500 100644 --- a/packages/fxa-auth-server/lib/metrics/glean/index.ts +++ b/packages/fxa-auth-server/lib/metrics/glean/index.ts @@ -10,7 +10,7 @@ import { import { version } from '../../../package.json'; import { createHash } from 'crypto'; import { AuthRequest } from '../../types'; -import * as AppError from '../../error'; +import { AppError } from '@fxa/accounts/errors'; import { clientId as clientIdValidator } from '../../oauth/validators'; import { MetricsContext } from '@fxa/shared/metrics/glean'; @@ -423,7 +423,7 @@ export function gleanMetrics(config: ConfigType) { knownIp: createEventFn('login_confirm_skip_for_known_ip'), newAccount: createEventFn('login_confirm_skip_for_new_account'), knownDevice: createEventFn('login_confirm_skip_for_known_device'), - } + }, }; } diff --git a/packages/fxa-auth-server/lib/monitoring.js b/packages/fxa-auth-server/lib/monitoring.js index 6979169f03..25e2cb6a53 100644 --- a/packages/fxa-auth-server/lib/monitoring.js +++ b/packages/fxa-auth-server/lib/monitoring.js @@ -10,7 +10,7 @@ const logger = require('./log')( 'configure-sentry' ); const { version } = require('../package.json'); -const { ignoreErrors } = require('./error'); +const { ignoreErrors } = require('@fxa/accounts/errors'); /** * Initialize sentry & otel diff --git a/packages/fxa-auth-server/lib/oauth/assertion.js b/packages/fxa-auth-server/lib/oauth/assertion.js index f151b1d2bc..6dba6aaf15 100644 --- a/packages/fxa-auth-server/lib/oauth/assertion.js +++ b/packages/fxa-auth-server/lib/oauth/assertion.js @@ -17,7 +17,7 @@ const Joi = require('joi'); const validators = require('./validators'); -const OauthError = require('./error'); +const { OauthError } = require('@fxa/accounts/errors'); const { config } = require('../../config'); const { verifyJWT } = require('../../lib/serverJWT'); diff --git a/packages/fxa-auth-server/lib/oauth/auth_client_management.js b/packages/fxa-auth-server/lib/oauth/auth_client_management.js index d3175482f8..8917553dfb 100644 --- a/packages/fxa-auth-server/lib/oauth/auth_client_management.js +++ b/packages/fxa-auth-server/lib/oauth/auth_client_management.js @@ -4,7 +4,7 @@ const ScopeSet = require('fxa-shared').oauth.scopes; -const OauthError = require('./error'); +const { OauthError } = require('@fxa/accounts/errors'); const token = require('./token'); const validators = require('./validators'); diff --git a/packages/fxa-auth-server/lib/oauth/authorized_clients.js b/packages/fxa-auth-server/lib/oauth/authorized_clients.js index 277a749a6b..1a23b681be 100644 --- a/packages/fxa-auth-server/lib/oauth/authorized_clients.js +++ b/packages/fxa-auth-server/lib/oauth/authorized_clients.js @@ -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/. */ -const OauthError = require('./error'); +const { OauthError } = require('@fxa/accounts/errors'); const oauthDB = require('./db'); const ScopeSet = require('fxa-shared').oauth.scopes; diff --git a/packages/fxa-auth-server/lib/oauth/client.js b/packages/fxa-auth-server/lib/oauth/client.js index 827091a4b2..bf8d4022b6 100644 --- a/packages/fxa-auth-server/lib/oauth/client.js +++ b/packages/fxa-auth-server/lib/oauth/client.js @@ -6,7 +6,7 @@ const crypto = require('crypto'); const buf = (v) => (Buffer.isBuffer(v) ? v : Buffer.from(v, 'hex')); const Joi = require('joi'); -const OauthError = require('./error'); +const { OauthError } = require('@fxa/accounts/errors'); const validators = require('./validators'); const db = require('./db'); const encrypt = require('fxa-shared/auth/encrypt'); @@ -59,7 +59,7 @@ module.exports.getClientCredentials = function getClientCredentials( // the Authorization header or request body, but not both. if (headers.authorization) { const authzMatch = validators.BASIC_AUTH_HEADER.exec(headers.authorization); - const err = new OauthError.invalidRequestParameter({ + const err = OauthError.invalidRequestParameter({ keys: ['authorization'], }); if (!authzMatch || creds.client_id || creds.client_secret) { @@ -106,7 +106,7 @@ module.exports.authenticateClient = async function authenticateClient( // and should never submit a client_secret. if (client.publicClient) { if (creds.client_secret) { - throw new OauthError.invalidRequestParameter({ keys: ['client_secret'] }); + throw OauthError.invalidRequestParameter({ keys: ['client_secret'] }); } return client; } @@ -114,7 +114,7 @@ module.exports.authenticateClient = async function authenticateClient( // Check client_secret against both current and previous stored secrets, // to allow for seamless rotation of the secret. if (!creds.client_secret) { - throw new OauthError.invalidRequestParameter({ keys: ['client_secret'] }); + throw OauthError.invalidRequestParameter({ keys: ['client_secret'] }); } const submitted = encrypt.hash(buf(creds.client_secret)); const stored = client.hashedSecret; diff --git a/packages/fxa-auth-server/lib/oauth/error.js b/packages/fxa-auth-server/lib/oauth/error.js deleted file mode 100644 index e9d42fba19..0000000000 --- a/packages/fxa-auth-server/lib/oauth/error.js +++ /dev/null @@ -1,387 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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/. */ - -const util = require('util'); -const hex = (v) => (Buffer.isBuffer(v) ? v.toString('hex') : v); - -const DEFAULTS = { - code: 500, - error: 'Internal Server Error', - errno: 999, - info: 'https://mozilla.github.io/ecosystem-platform/api#section/Errors', - message: 'Unspecified error', -}; - -function merge(target, other) { - var keys = Object.keys(other || {}); - for (var i = 0; i < keys.length; i++) { - target[keys[i]] = other[keys[i]]; - } -} - -// Deprecated. New error types should be defined in lib/error.js -function OauthError(options, extra, headers) { - this.message = options.message || DEFAULTS.message; - this.isBoom = true; - if (options.stack) { - this.stack = options.stack; - } else { - Error.captureStackTrace(this, OauthError); - } - this.errno = options.errno || DEFAULTS.errno; - this.output = { - statusCode: options.code || DEFAULTS.code, - payload: { - code: options.code || DEFAULTS.code, - errno: this.errno, - error: options.error || DEFAULTS.error, - message: this.message, - info: options.info || DEFAULTS.info, - }, - headers: headers || {}, - }; - merge(this.output.payload, extra); -} -util.inherits(OauthError, Error); - -OauthError.prototype.toString = function () { - return 'Error: ' + this.message; -}; - -OauthError.prototype.header = function (name, value) { - this.output.headers[name] = value; -}; - -OauthError.isOauthRoute = function isOauthRoute(path) { - const routes = require('../routes/oauth')(); - // ironically, routes that include "oauth" are considered auth-server routes - return ( - // For now we use a fragile heuristic that all oauth routes set cors config - // TODO: when we merge oauth errors into auth, rethink this. - routes.findIndex((r) => `/v1${r.path}` === path && r.config.cors) > -1 - ); -}; - -OauthError.translate = function translate(response) { - if (response instanceof OauthError) { - return response; - } - - var error; - var payload = response.output.payload; - if (payload.validation) { - error = OauthError.invalidRequestParameter(payload.validation); - } else if (payload.statusCode === 415) { - error = OauthError.invalidContentType(); - } else { - error = new OauthError({ - message: payload.message, - code: payload.statusCode, - error: payload.error, - errno: payload.errno, - stack: response.stack, - }); - } - - return error; -}; - -OauthError.prototype.backtrace = function (traced) { - this.output.payload.log = traced; -}; - -OauthError.unexpectedError = function unexpectedError() { - return new OauthError({}); -}; - -OauthError.unknownClient = function unknownClient(clientId) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 101, - message: 'Unknown client', - }, - { - clientId: hex(clientId), - } - ); -}; - -OauthError.incorrectSecret = function incorrectSecret(clientId) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 102, - message: 'Incorrect secret', - }, - { - clientId: hex(clientId), - } - ); -}; - -OauthError.incorrectRedirect = function incorrectRedirect(uri) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 103, - message: 'Incorrect redirect_uri', - }, - { - redirectUri: uri, - } - ); -}; - -OauthError.invalidAssertion = function invalidAssertion() { - return new OauthError({ - code: 401, - error: 'Bad Request', - errno: 104, - message: 'Invalid assertion', - }); -}; - -OauthError.unknownCode = function unknownCode(code) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 105, - message: 'Unknown code', - }, - { - requestCode: code, - } - ); -}; - -OauthError.mismatchCode = function mismatchCode(code, clientId) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 106, - message: 'Incorrect code', - }, - { - requestCode: hex(code), - client: hex(clientId), - } - ); -}; - -OauthError.expiredCode = function expiredCode(code, expiredAt) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 107, - message: 'Expired code', - }, - { - requestCode: hex(code), - expiredAt: expiredAt, - } - ); -}; - -OauthError.invalidToken = function invalidToken() { - return new OauthError({ - code: 400, - error: 'Bad Request', - errno: 108, - message: 'Invalid token', - }); -}; - -OauthError.invalidRequestParameter = function invalidRequestParameter(val) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 109, - message: 'Invalid request parameter', - }, - { - validation: val, - } - ); -}; - -OauthError.invalidResponseType = function invalidResponseType() { - return new OauthError({ - code: 400, - error: 'Bad Request', - errno: 110, - message: 'Invalid response_type', - }); -}; - -OauthError.unauthorized = function unauthorized(reason) { - return new OauthError( - { - code: 401, - error: 'Unauthorized', - errno: 111, - message: 'Unauthorized for route', - }, - { - detail: reason, - } - ); -}; - -OauthError.forbidden = function forbidden() { - return new OauthError({ - code: 403, - error: 'Forbidden', - errno: 112, - message: 'Forbidden', - }); -}; - -OauthError.invalidContentType = function invalidContentType() { - 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', - }); -}; - -OauthError.invalidScopes = function invalidScopes(scopes) { - return new OauthError( - { - code: 400, - error: 'Invalid scopes', - errno: 114, - message: 'Requested scopes are not allowed', - }, - { - invalidScopes: scopes, - } - ); -}; - -OauthError.expiredToken = function expiredToken(expiredAt) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 115, - message: 'Expired token', - }, - { - expiredAt: expiredAt, - } - ); -}; - -OauthError.notPublicClient = function notPublicClient(clientId) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 116, - message: 'Not a public client', - }, - { - clientId: hex(clientId), - } - ); -}; - -OauthError.mismatchCodeChallenge = function mismatchCodeChallenge( - pkceHashValue -) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 117, - message: 'Incorrect code_challenge', - }, - { - requestCodeChallenge: pkceHashValue, - } - ); -}; - -OauthError.missingPkceParameters = function missingPkceParameters() { - return new OauthError({ - code: 400, - error: 'PKCE parameters missing', - errno: 118, - message: 'Public clients require PKCE OAuth parameters', - }); -}; - -OauthError.staleAuthAt = function staleAuthAt(authAt) { - return new OauthError( - { - code: 401, - error: 'Bad Request', - errno: 119, - message: 'Stale authentication timestamp', - }, - { - authAt: authAt, - } - ); -}; - -OauthError.mismatchAcr = function mismatchAcr(foundValue) { - return new OauthError( - { - code: 400, - error: 'Bad Request', - errno: 120, - message: 'Mismatch acr value', - }, - { foundValue } - ); -}; - -OauthError.invalidGrantType = function invalidGrantType() { - return new OauthError({ - code: 400, - error: 'Bad Request', - errno: 121, - message: 'Invalid grant_type', - }); -}; - -OauthError.unknownToken = function unknownToken() { - return new OauthError({ - code: 400, - error: 'Bad Request', - errno: 122, - message: 'Unknown token', - }); -}; - -// N.B. `errno: 201` is traditionally our generic "service unavailable" error, -// so let's reserve it for that purpose here as well. - -OauthError.disabledClient = function disabledClient(clientId) { - return new OauthError( - { - code: 503, - error: 'Client Disabled', - errno: 202, // TODO reconcile this with the auth-server version - message: 'This client has been temporarily disabled', - }, - { clientId } - ); -}; - -// Deprecated. New error types should be defined in lib/error.js - -module.exports = OauthError; diff --git a/packages/fxa-auth-server/lib/oauth/grant.js b/packages/fxa-auth-server/lib/oauth/grant.js index 8aa36b79d0..962125fa93 100644 --- a/packages/fxa-auth-server/lib/oauth/grant.js +++ b/packages/fxa-auth-server/lib/oauth/grant.js @@ -6,7 +6,7 @@ const { Container } = require('typedi'); const { CapabilityService } = require('../payments/capability'); const { config } = require('../../config'); -const OauthError = require('./error'); +const { OauthError } = require('@fxa/accounts/errors'); const db = require('./db'); const util = require('./util'); const ScopeSet = require('fxa-shared').oauth.scopes; @@ -46,7 +46,6 @@ const TRUSTED_CLIENT_ALLOWED_SCOPES = ScopeSet.fromArray([ 'profile:subscriptions', ]); - /** @type {CapabilityService} */ let capabilityService = undefined; @@ -116,9 +115,13 @@ module.exports.validateRequestedGrant = async function validateRequestedGrant( // For trusted clients, validate scopes against clients trusted scopes if (client.trusted) { const clientScopeSet = ScopeSet.fromString(client.allowedScopes || ''); - const trustedClientAllowedScopes = clientScopeSet.union(TRUSTED_CLIENT_ALLOWED_SCOPES); + const trustedClientAllowedScopes = clientScopeSet.union( + TRUSTED_CLIENT_ALLOWED_SCOPES + ); - const invalidScopes = requestedGrant.scope.difference(trustedClientAllowedScopes); + const invalidScopes = requestedGrant.scope.difference( + trustedClientAllowedScopes + ); if (!invalidScopes.isEmpty()) { if (config.get('oauthServer.strictScopeValidation')) { // Strict mode: remove invalid scopes diff --git a/packages/fxa-auth-server/lib/oauth/jwt_access_token.js b/packages/fxa-auth-server/lib/oauth/jwt_access_token.js index 3f3ac9088a..4ed64b200f 100644 --- a/packages/fxa-auth-server/lib/oauth/jwt_access_token.js +++ b/packages/fxa-auth-server/lib/oauth/jwt_access_token.js @@ -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/. */ -const OauthError = require('./error'); +const { OauthError } = require('@fxa/accounts/errors'); const jwt = require('./jwt'); const sub = require('./jwt_sub'); const { OAUTH_SCOPE_OLD_SYNC } = require('fxa-shared/oauth/constants'); @@ -23,8 +23,8 @@ exports.create = async function generateJWTAccessToken(accessToken, grant) { const audience = grant.resource ? [clientId, grant.resource] : grant.scope.contains(OAUTH_SCOPE_OLD_SYNC) - ? TOKEN_SERVER_URL - : clientId; + ? TOKEN_SERVER_URL + : clientId; // Claims list from: // https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt#section-2.2 diff --git a/packages/fxa-auth-server/lib/oauth/jwt_id_token.js b/packages/fxa-auth-server/lib/oauth/jwt_id_token.js index 7101775332..2d7ccb77d1 100644 --- a/packages/fxa-auth-server/lib/oauth/jwt_id_token.js +++ b/packages/fxa-auth-server/lib/oauth/jwt_id_token.js @@ -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/. */ -const OauthError = require('./error'); +const { OauthError } = require('@fxa/accounts/errors'); const jwt = require('./jwt'); /** diff --git a/packages/fxa-auth-server/lib/oauth/token.js b/packages/fxa-auth-server/lib/oauth/token.js index 774f7782d9..d0213045a8 100644 --- a/packages/fxa-auth-server/lib/oauth/token.js +++ b/packages/fxa-auth-server/lib/oauth/token.js @@ -4,7 +4,7 @@ const ScopeSet = require('fxa-shared').oauth.scopes; -const OauthError = require('./error'); +const { OauthError } = require('@fxa/accounts/errors'); const { config } = require('../../config'); const db = require('./db'); const encrypt = require('fxa-shared/auth/encrypt'); diff --git a/packages/fxa-auth-server/lib/oauth/util.js b/packages/fxa-auth-server/lib/oauth/util.js index 5e2945116f..f78d5b2163 100644 --- a/packages/fxa-auth-server/lib/oauth/util.js +++ b/packages/fxa-auth-server/lib/oauth/util.js @@ -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/. */ -const AuthError = require('../error'); +const { AppError: AuthError } = require('@fxa/accounts/errors'); const { signJWT } = require('../serverJWT'); const crypto = require('crypto'); diff --git a/packages/fxa-auth-server/lib/payments/capability.ts b/packages/fxa-auth-server/lib/payments/capability.ts index 4dba286879..559e0f0fdf 100644 --- a/packages/fxa-auth-server/lib/payments/capability.ts +++ b/packages/fxa-auth-server/lib/payments/capability.ts @@ -32,7 +32,7 @@ import { import * as Sentry from '@sentry/node'; import { SeverityLevel } from '@sentry/core'; -import error from '../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { authEvents } from '../events'; import { AppConfig, AuthLogger, AuthRequest } from '../types'; import { ConfigType } from '../../config'; diff --git a/packages/fxa-auth-server/lib/payments/configuration/manager.ts b/packages/fxa-auth-server/lib/payments/configuration/manager.ts index 10616b7f60..cdc4c4344c 100644 --- a/packages/fxa-auth-server/lib/payments/configuration/manager.ts +++ b/packages/fxa-auth-server/lib/payments/configuration/manager.ts @@ -5,7 +5,7 @@ import { randomUUID } from 'crypto'; import { Logger } from 'mozlog'; import { Container } from 'typedi'; -import errors from '../../error'; +import { AppError as errors } from '@fxa/accounts/errors'; import { AppConfig, AuthFirestore, AuthLogger } from '../../types'; import { diff --git a/packages/fxa-auth-server/lib/payments/iap/apple-app-store/subscriptions.ts b/packages/fxa-auth-server/lib/payments/iap/apple-app-store/subscriptions.ts index d850f894bf..361961429b 100644 --- a/packages/fxa-auth-server/lib/payments/iap/apple-app-store/subscriptions.ts +++ b/packages/fxa-auth-server/lib/payments/iap/apple-app-store/subscriptions.ts @@ -5,7 +5,7 @@ import { AppendedAppStoreSubscriptionPurchase } from 'fxa-shared/payments/iap/ap import { MozillaSubscriptionTypes } from 'fxa-shared/subscriptions/types'; import Container from 'typedi'; -import { internalValidationError } from '../../../../lib/error'; +import { AppError } from '@fxa/accounts/errors'; import { AppConfig } from '../../../types'; import { StripeHelper } from '../../stripe'; import { SubscriptionsService } from '../types'; @@ -20,7 +20,7 @@ export class AppStoreSubscriptions constructor() { const config = Container.get(AppConfig); if (!config.subscriptions.enabled) { - throw internalValidationError( + throw AppError.internalValidationError( 'AppStoreSubscriptions', {}, new Error( @@ -30,7 +30,7 @@ export class AppStoreSubscriptions } if (!Container.has(StripeHelper)) { - throw internalValidationError( + throw AppError.internalValidationError( 'AppStoreSubscriptions', {}, new Error( diff --git a/packages/fxa-auth-server/lib/payments/iap/google-play/subscriptions.ts b/packages/fxa-auth-server/lib/payments/iap/google-play/subscriptions.ts index 809e1dcc1d..0bf8f745bb 100644 --- a/packages/fxa-auth-server/lib/payments/iap/google-play/subscriptions.ts +++ b/packages/fxa-auth-server/lib/payments/iap/google-play/subscriptions.ts @@ -4,7 +4,7 @@ import { MozillaSubscriptionTypes } from 'fxa-shared/subscriptions/types'; import Container from 'typedi'; -import { internalValidationError } from '../../../../lib/error'; +import { AppError } from '@fxa/accounts/errors'; import { AppConfig } from '../../../types'; import { StripeHelper } from '../../stripe'; import { SubscriptionsService } from '../types'; @@ -20,7 +20,7 @@ export class PlaySubscriptions constructor() { const config = Container.get(AppConfig); if (!config.subscriptions.enabled) { - throw internalValidationError( + throw AppError.internalValidationError( 'PlaySubscriptions', {}, new Error( @@ -30,7 +30,7 @@ export class PlaySubscriptions } if (!Container.has(StripeHelper)) { - throw internalValidationError( + throw AppError.internalValidationError( 'PlaySubscriptions', {}, new Error( diff --git a/packages/fxa-auth-server/lib/payments/iap/iap-config.ts b/packages/fxa-auth-server/lib/payments/iap/iap-config.ts index ef9afa2e01..d37b5ad2c4 100644 --- a/packages/fxa-auth-server/lib/payments/iap/iap-config.ts +++ b/packages/fxa-auth-server/lib/payments/iap/iap-config.ts @@ -5,7 +5,7 @@ import { Firestore } from '@google-cloud/firestore'; import { Container } from 'typedi'; import { TypedCollectionReference } from '@fxa/vendored/typesafe-node-firestore'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { AppConfig, AuthFirestore, AuthLogger } from '../../types'; import { IapConfig } from './types'; diff --git a/packages/fxa-auth-server/lib/payments/paypal/helper.ts b/packages/fxa-auth-server/lib/payments/paypal/helper.ts index 31d546866e..84fe4e2869 100644 --- a/packages/fxa-auth-server/lib/payments/paypal/helper.ts +++ b/packages/fxa-auth-server/lib/payments/paypal/helper.ts @@ -21,7 +21,7 @@ import { TransactionSearchOptions, TransactionStatus, } from '@fxa/payments/paypal'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { CurrencyHelper } from '../currencies'; import { StripeHelper } from '../stripe'; import { RefusedError } from './error'; @@ -525,7 +525,7 @@ export class PayPalHelper { try { [transactionResponse] = (await Promise.all(promises)) as [ ChargeResponse, - any + any, ]; } catch (err) { if (PayPalClientError.hasPayPalNVPError(err) && !batchProcessing) { @@ -672,14 +672,19 @@ export class PayPalHelper { /** * Refunds the invoice passed, throwing an error on any issue encountered. */ - public async refundInvoice(invoice: Stripe.Invoice, behavior: { - refundType: RefundType.Full, - } | { - refundType: RefundType.Partial, - amount: number - } = { - refundType: RefundType.Full, - }) { + public async refundInvoice( + invoice: Stripe.Invoice, + behavior: + | { + refundType: RefundType.Full; + } + | { + refundType: RefundType.Partial; + amount: number; + } = { + refundType: RefundType.Full, + } + ) { this.log.debug('PayPalHelper.refundInvoice', { invoiceId: invoice.id, }); @@ -707,12 +712,25 @@ export class PayPalHelper { throw new RefundError('Invoice already refunded with PayPal'); } - if (behavior.refundType === RefundType.Partial && behavior.amount >= invoice.amount_paid) { - throw new RefundError('Partial refunds must be less than the amount paid on the invoice'); + if ( + behavior.refundType === RefundType.Partial && + behavior.amount >= invoice.amount_paid + ) { + throw new RefundError( + 'Partial refunds must be less than the amount paid on the invoice' + ); } - const amount = behavior.refundType === RefundType.Partial ? behavior.amount : undefined; + const amount = + behavior.refundType === RefundType.Partial + ? behavior.amount + : undefined; - await this.issueRefund(invoice, transactionId, behavior.refundType, amount); + await this.issueRefund( + invoice, + transactionId, + behavior.refundType, + amount + ); this.log.info('refundInvoice', { invoiceId: invoice.id, diff --git a/packages/fxa-auth-server/lib/payments/paypal/processor.ts b/packages/fxa-auth-server/lib/payments/paypal/processor.ts index 5d5813d877..3d6d935f1e 100644 --- a/packages/fxa-auth-server/lib/payments/paypal/processor.ts +++ b/packages/fxa-auth-server/lib/payments/paypal/processor.ts @@ -8,7 +8,7 @@ import { Container } from 'typedi'; import { PayPalClientError } from '@fxa/payments/paypal'; import { ConfigType } from '../../../config'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { StripeWebhookHandler } from '../../routes/subscriptions/stripe-webhook'; import { reportSentryError } from '../../sentry'; import { AuthLogger } from '../../types'; diff --git a/packages/fxa-auth-server/lib/payments/stripe.ts b/packages/fxa-auth-server/lib/payments/stripe.ts index 949ff89859..24a66b6c5e 100644 --- a/packages/fxa-auth-server/lib/payments/stripe.ts +++ b/packages/fxa-auth-server/lib/payments/stripe.ts @@ -61,7 +61,7 @@ import { Stripe } from 'stripe'; import { Container } from 'typedi'; import { ConfigType } from '../../config'; -import error from '../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { GoogleMapsService } from '../google-maps-services'; import Redis from '../redis'; import { subscriptionProductMetadataValidator } from '../routes/validators'; @@ -2181,7 +2181,7 @@ export class StripeHelper extends StripeHelperBase { subscriptionId ); if (!subscription) { - throw error.unknownSubscription(); + throw error.unknownSubscription(subscriptionId); } await this.updateSubscriptionAndBackfill(subscription, { @@ -2213,7 +2213,7 @@ export class StripeHelper extends StripeHelperBase { subscriptionId ); if (!subscription) { - throw error.unknownSubscription(); + throw error.unknownSubscription(subscriptionId); } if (!ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status)) { @@ -3545,8 +3545,10 @@ export class StripeHelper extends StripeHelperBase { * Process a invoice event that needs to be saved to Firestore. */ async processInvoiceEventToFirestore(event: Stripe.Event) { - if (event.data.object.object !== "invoice") { - throw new Error("processInvoiceEventToFirestore must receive only invoice types"); + if (event.data.object.object !== 'invoice') { + throw new Error( + 'processInvoiceEventToFirestore must receive only invoice types' + ); } const eventData = event.data.object; @@ -3554,15 +3556,21 @@ export class StripeHelper extends StripeHelperBase { if (!invoiceId) throw new Error('Invoice ID must be specified'); const customerId = eventData.customer; if (typeof customerId !== 'string') { - throw new Error("customerId must be a string"); + throw new Error('customerId must be a string'); } try { - await this.stripeFirestore.fetchAndInsertInvoice(invoiceId, event.created); + await this.stripeFirestore.fetchAndInsertInvoice( + invoiceId, + event.created + ); } catch (err) { if (err.name === FirestoreStripeError.FIRESTORE_CUSTOMER_NOT_FOUND) { await this.stripeFirestore.fetchAndInsertCustomer(customerId); - await this.stripeFirestore.fetchAndInsertInvoice(invoiceId, event.created); + await this.stripeFirestore.fetchAndInsertInvoice( + invoiceId, + event.created + ); return; } throw err; @@ -3586,7 +3594,10 @@ export class StripeHelper extends StripeHelperBase { const paymentMethodId = (event.data.object as Stripe.PaymentMethod).id; try { - await this.stripeFirestore.fetchAndInsertPaymentMethod(paymentMethodId, event.created); + await this.stripeFirestore.fetchAndInsertPaymentMethod( + paymentMethodId, + event.created + ); } catch (err) { if ( !( diff --git a/packages/fxa-auth-server/lib/pushbox/index.ts b/packages/fxa-auth-server/lib/pushbox/index.ts index 863f6c9110..3061329987 100644 --- a/packages/fxa-auth-server/lib/pushbox/index.ts +++ b/packages/fxa-auth-server/lib/pushbox/index.ts @@ -20,7 +20,7 @@ import { StatsD } from 'hot-shots'; import { performance } from 'perf_hooks'; import { ConfigType } from '../../config'; -import error from '../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { PushboxDB } from './db'; // Pushbox stores strings, so these are a little pair diff --git a/packages/fxa-auth-server/lib/routes/account.ts b/packages/fxa-auth-server/lib/routes/account.ts index 79fe2cc0a6..e832b1ddcc 100644 --- a/packages/fxa-auth-server/lib/routes/account.ts +++ b/packages/fxa-auth-server/lib/routes/account.ts @@ -22,7 +22,7 @@ import MISC_DOCS from '../../docs/swagger/misc-api'; import DESCRIPTION from '../../docs/swagger/shared/descriptions'; import authMethods from '../authMethods'; import random from '../crypto/random'; -import error from '../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { getClientById } from '../oauth/client'; import { generateAccessToken } from '../oauth/grant'; import jwt from '../oauth/jwt'; diff --git a/packages/fxa-auth-server/lib/routes/attached-clients.js b/packages/fxa-auth-server/lib/routes/attached-clients.js index f95300a67e..c9f5ff6204 100644 --- a/packages/fxa-auth-server/lib/routes/attached-clients.js +++ b/packages/fxa-auth-server/lib/routes/attached-clients.js @@ -7,7 +7,7 @@ const isA = require('joi'); const validators = require('./validators'); const authorizedClients = require('../oauth/authorized_clients'); -const error = require('../error'); +const { AppError: error } = require('@fxa/accounts/errors'); const HEX_STRING = validators.HEX_STRING; const DEVICES_SCHEMA = require('../devices').schema; diff --git a/packages/fxa-auth-server/lib/routes/auth-schemes/auth-oauth.js b/packages/fxa-auth-server/lib/routes/auth-schemes/auth-oauth.js index 737a3ed7ce..cae07237f5 100644 --- a/packages/fxa-auth-server/lib/routes/auth-schemes/auth-oauth.js +++ b/packages/fxa-auth-server/lib/routes/auth-schemes/auth-oauth.js @@ -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/. */ -const AppError = require('../../error'); +const { AppError } = require('@fxa/accounts/errors'); const token = require('../../oauth/token'); const authName = 'fxa-oauth'; diff --git a/packages/fxa-auth-server/lib/routes/auth-schemes/google-oidc.ts b/packages/fxa-auth-server/lib/routes/auth-schemes/google-oidc.ts index 5b1367f164..9b4c8e56c3 100644 --- a/packages/fxa-auth-server/lib/routes/auth-schemes/google-oidc.ts +++ b/packages/fxa-auth-server/lib/routes/auth-schemes/google-oidc.ts @@ -4,7 +4,7 @@ import { OAuth2Client } from 'google-auth-library'; import { Request, ResponseToolkit } from '@hapi/hapi'; -import AppError from '../../error'; +import { AppError } from '@fxa/accounts/errors'; export const strategy = ({ aud, diff --git a/packages/fxa-auth-server/lib/routes/auth-schemes/hawk-fxa-token.js b/packages/fxa-auth-server/lib/routes/auth-schemes/hawk-fxa-token.js index 490b827d7d..83ec2620f2 100644 --- a/packages/fxa-auth-server/lib/routes/auth-schemes/hawk-fxa-token.js +++ b/packages/fxa-auth-server/lib/routes/auth-schemes/hawk-fxa-token.js @@ -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/. */ -const AppError = require('../../error'); +const { AppError } = require('@fxa/accounts/errors'); const Boom = require('@hapi/boom'); // The following regexes and Hawk header parsing are taken from the Hawk library. diff --git a/packages/fxa-auth-server/lib/routes/auth-schemes/mfa.ts b/packages/fxa-auth-server/lib/routes/auth-schemes/mfa.ts index 69ef9ee7bd..316eb43777 100644 --- a/packages/fxa-auth-server/lib/routes/auth-schemes/mfa.ts +++ b/packages/fxa-auth-server/lib/routes/auth-schemes/mfa.ts @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Request, ResponseToolkit } from '@hapi/hapi'; -import * as AppError from '../../error'; +import { AppError } from '@fxa/accounts/errors'; import { ConfigType } from '../../../config/index'; import * as jwt from 'jsonwebtoken'; import { Account } from 'fxa-shared/db/models/auth'; diff --git a/packages/fxa-auth-server/lib/routes/auth-schemes/refresh-token.js b/packages/fxa-auth-server/lib/routes/auth-schemes/refresh-token.js index 1e5aa6405f..45837fb5c1 100644 --- a/packages/fxa-auth-server/lib/routes/auth-schemes/refresh-token.js +++ b/packages/fxa-auth-server/lib/routes/auth-schemes/refresh-token.js @@ -4,7 +4,7 @@ 'use strict'; -const AppError = require('../../error'); +const { AppError } = require('@fxa/accounts/errors'); const joi = require('joi'); const validators = require('../validators'); const { BEARER_AUTH_REGEX } = validators; @@ -26,15 +26,14 @@ module.exports = function schemeRefreshTokenScheme(config, db) { return { async authenticate(request, h) { if (config.oauth.deviceAccessEnabled === false) { - throw new AppError.featureNotEnabled(); + throw AppError.featureNotEnabled(); } const bearerMatch = BEARER_AUTH_REGEX.exec( request.headers.authorization ); - const bearerMatchErr = new AppError.invalidRequestParameter( - 'authorization' - ); + const bearerMatchErr = + AppError.invalidRequestParameter('authorization'); const bearerToken = bearerMatch && bearerMatch[1]; if (bearerToken) { joi.attempt(bearerMatch[1], validators.refreshToken, bearerMatchErr); @@ -45,7 +44,7 @@ module.exports = function schemeRefreshTokenScheme(config, db) { const tokenId = encrypt.hash(bearerToken); const refreshToken = await oauthDB.getRefreshToken(tokenId); if (!refreshToken) { - return h.unauthenticated(new AppError.invalidToken()); + return h.unauthenticated(AppError.invalidToken()); } if ( !refreshToken.scope.intersects(ALLOWED_REFRESH_TOKEN_SCHEME_SCOPES) @@ -71,7 +70,7 @@ module.exports = function schemeRefreshTokenScheme(config, db) { credentials.client = await client.getClientById(refreshToken.clientId); if (!credentials.client || !credentials.client.publicClient) { - return h.unauthenticated(new AppError.notPublicClient()); + return h.unauthenticated(AppError.notPublicClient()); } const devices = await db.devices(credentials.uid); diff --git a/packages/fxa-auth-server/lib/routes/auth-schemes/shared-secret.js b/packages/fxa-auth-server/lib/routes/auth-schemes/shared-secret.js index b0c6aafa4f..e5a79b81ee 100644 --- a/packages/fxa-auth-server/lib/routes/auth-schemes/shared-secret.js +++ b/packages/fxa-auth-server/lib/routes/auth-schemes/shared-secret.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const Boom = require('@hapi/boom'); -const AppError = require('../../error'); +const { AppError } = require('@fxa/accounts/errors'); const crypto = require('crypto'); const constantTimeCompare = (subject, object) => { diff --git a/packages/fxa-auth-server/lib/routes/auth-schemes/verified-session-token.js b/packages/fxa-auth-server/lib/routes/auth-schemes/verified-session-token.js index dc9575b4de..afdc4bf550 100644 --- a/packages/fxa-auth-server/lib/routes/auth-schemes/verified-session-token.js +++ b/packages/fxa-auth-server/lib/routes/auth-schemes/verified-session-token.js @@ -4,7 +4,7 @@ 'use strict'; -const AppError = require('../../error'); +const { AppError } = require('@fxa/accounts/errors'); const authMethods = require('../../authMethods'); const { parseAuthorizationHeader } = require('./hawk-fxa-token'); diff --git a/packages/fxa-auth-server/lib/routes/cms.ts b/packages/fxa-auth-server/lib/routes/cms.ts index 391516d836..36ea6bccaf 100644 --- a/packages/fxa-auth-server/lib/routes/cms.ts +++ b/packages/fxa-auth-server/lib/routes/cms.ts @@ -6,7 +6,7 @@ import crypto from 'crypto'; import { ConfigType } from '../../config'; import { AuthLogger, AuthRequest } from '../types'; import { Container } from 'typedi'; -import AppError from '../error'; +import { AppError } from '@fxa/accounts/errors'; import isA from 'joi'; import validators from './validators'; import { StatsD } from 'hot-shots'; @@ -28,7 +28,12 @@ export class CMSHandler { this.config = config; this.statsd = statsD; this.log = log; - this.localization = new CMSLocalization(log, config, this.cmsManager, this.statsd); + this.localization = new CMSLocalization( + log, + config, + this.cmsManager, + this.statsd + ); } ensureCmsManager() { @@ -109,7 +114,7 @@ export class CMSHandler { this.log.info('cms.getLocalizedConfig.noBaseConfig', { clientId, entrypoint, - locale + locale, }); return {}; } @@ -120,33 +125,39 @@ export class CMSHandler { clientId, entrypoint, locale, - reason: locale === 'en' ? 'default-locale' : 'localization-disabled' + reason: locale === 'en' ? 'default-locale' : 'localization-disabled', }); return baseConfig; } // 4. Try to fetch localized FTL content with fallback logic - const ftlContent = await this.localization.fetchLocalizedFtlWithFallback(locale); + const ftlContent = + await this.localization.fetchLocalizedFtlWithFallback(locale); // 5. If no localized content, return base config if (!ftlContent) { this.log.info('cms.getLocalizedConfig.fallbackToBase', { clientId, entrypoint, - locale + locale, }); this.statsd.increment('cms.getLocalizedConfig.fallback'); return baseConfig; } // 6. Merge base config with localized data - const localizedConfig = await this.localization.mergeConfigs(baseConfig, ftlContent, clientId, entrypoint); + const localizedConfig = await this.localization.mergeConfigs( + baseConfig, + ftlContent, + clientId, + entrypoint + ); this.log.info('cms.getLocalizedConfig.success', { clientId, entrypoint, locale, - ftlContentLength: ftlContent.length + ftlContentLength: ftlContent.length, }); this.statsd.increment('cms.getLocalizedConfig.success'); @@ -157,7 +168,7 @@ export class CMSHandler { clientId, entrypoint, locale, - error: error.message + error: error.message, }); this.statsd.increment('cms.getLocalizedConfig.error'); @@ -169,7 +180,7 @@ export class CMSHandler { clientId, entrypoint, locale, - error: fallbackError.message + error: fallbackError.message, }); return {}; } @@ -223,7 +234,11 @@ export class CMSHandler { // Only create PRs when entries are actually published const allowedEvents = ['entry.publish']; - if (!eventType || !allowedEvents.includes(eventType) || webhookPayload.model !== 'relying-party') { + if ( + !eventType || + !allowedEvents.includes(eventType) || + webhookPayload.model !== 'relying-party' + ) { this.log.info('cms.strapiWebhook.skipped', { eventType, reason: 'Event not in allowed list or not relying-party model', @@ -251,7 +266,8 @@ export class CMSHandler { await this.localization.validateGitHubConfig(); // Generate FTL content for all entries - const ftlContent = this.localization.generateFtlContentFromEntries(allEntries); + const ftlContent = + this.localization.generateFtlContentFromEntries(allEntries); // Check for existing PR const existingPr = await this.localization.findExistingPR( @@ -408,7 +424,7 @@ export const cmsRoutes = ( }, handler: async (request: AuthRequest) => cmsHandler.handleCacheInvalidationWebhook(request), - } + }, ]; }; diff --git a/packages/fxa-auth-server/lib/routes/defaults.js b/packages/fxa-auth-server/lib/routes/defaults.js index a099950c6d..a7a1a4ee7e 100644 --- a/packages/fxa-auth-server/lib/routes/defaults.js +++ b/packages/fxa-auth-server/lib/routes/defaults.js @@ -4,7 +4,7 @@ 'use strict'; -const error = require('../error'); +const { AppError: error } = require('@fxa/accounts/errors'); const getVersion = require('../version').getVersion; diff --git a/packages/fxa-auth-server/lib/routes/devices-and-sessions.js b/packages/fxa-auth-server/lib/routes/devices-and-sessions.js index e73bd212c7..27b49fd9bd 100644 --- a/packages/fxa-auth-server/lib/routes/devices-and-sessions.js +++ b/packages/fxa-auth-server/lib/routes/devices-and-sessions.js @@ -8,7 +8,7 @@ const { URL } = require('url'); const Ajv = require('ajv'); const ajv = new Ajv(); const hex = (v) => (Buffer.isBuffer(v) ? v.toString('hex') : v); -const error = require('../error'); +const { AppError: error } = require('@fxa/accounts/errors'); const fs = require('fs'); const isA = require('joi'); const path = require('path'); @@ -249,7 +249,7 @@ module.exports = ( config.oauth.deviceCommandsEnabled === false && credentials.refreshTokenId ) { - throw new error.featureNotEnabled(); + throw error.featureNotEnabled(); } if (!deviceId) { @@ -262,7 +262,7 @@ module.exports = ( uaOSVersion: credentials.uaOSVersion, }); - throw new error.unknownDevice(); + throw error.unknownDevice(); } const response = await pushbox.retrieve(uid, deviceId, limit, index); @@ -329,7 +329,7 @@ module.exports = ( config.oauth.deviceCommandsEnabled === false && credentials.refreshTokenId ) { - throw new error.featureNotEnabled(); + throw error.featureNotEnabled(); } await customs.checkAuthenticated( @@ -606,7 +606,7 @@ module.exports = ( config.oauth.deviceCommandsEnabled === false && credentials.refreshTokenId ) { - throw new error.featureNotEnabled(); + throw error.featureNotEnabled(); } // If this request is using a session token we bump the last access time diff --git a/packages/fxa-auth-server/lib/routes/emails.js b/packages/fxa-auth-server/lib/routes/emails.js index 177c436113..1204d95551 100644 --- a/packages/fxa-auth-server/lib/routes/emails.js +++ b/packages/fxa-auth-server/lib/routes/emails.js @@ -6,7 +6,7 @@ const butil = require('../crypto/butil'); const emailUtils = require('./utils/email'); -const error = require('../error'); +const { AppError: error } = require('@fxa/accounts/errors'); const isA = require('joi'); const random = require('../crypto/random'); const Sentry = require('@sentry/node'); diff --git a/packages/fxa-auth-server/lib/routes/index.js b/packages/fxa-auth-server/lib/routes/index.js index 7d17e13b1c..2299fc0c7e 100644 --- a/packages/fxa-auth-server/lib/routes/index.js +++ b/packages/fxa-auth-server/lib/routes/index.js @@ -89,6 +89,10 @@ module.exports = function ( statsd, glean ); + const oauthRoutes = oauth.map((route) => ({ + path: route.path, + config: route.config || {}, + })); const devicesSessions = require('./devices-and-sessions')( log, db, @@ -305,6 +309,7 @@ module.exports = function ( r.path = basePath + r.path; }); const allRoutes = defaults.concat(idp, v1Routes); + allRoutes.oauthRoutes = oauthRoutes; allRoutes.forEach((r) => { // Default auth.payload to 'optional' for all authenticated routes. diff --git a/packages/fxa-auth-server/lib/routes/linked-accounts.ts b/packages/fxa-auth-server/lib/routes/linked-accounts.ts index 1b2573cb11..d167789e95 100644 --- a/packages/fxa-auth-server/lib/routes/linked-accounts.ts +++ b/packages/fxa-auth-server/lib/routes/linked-accounts.ts @@ -17,7 +17,7 @@ import { import THIRD_PARTY_AUTH_DOCS from '../../docs/swagger/third-party-auth-api'; import isA from 'joi'; import DESCRIPTION from '../../docs/swagger/shared/descriptions'; -import error from '../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { schema as METRICS_CONTEXT_SCHEMA } from '../metrics/context'; import { getGooglePublicKey, diff --git a/packages/fxa-auth-server/lib/routes/mfa.ts b/packages/fxa-auth-server/lib/routes/mfa.ts index 2c1b891ef1..6f9af2b504 100644 --- a/packages/fxa-auth-server/lib/routes/mfa.ts +++ b/packages/fxa-auth-server/lib/routes/mfa.ts @@ -13,7 +13,7 @@ import { AuthRequest, SessionTokenAuthCredential } from '../types'; import { recordSecurityEvent } from './utils/security-event'; import { ConfigType } from '../../config'; import { OtpUtils } from './utils/otp'; -import * as AppError from '../error'; +import { AppError } from '@fxa/accounts/errors'; /** Customs interface for mfa specific operations. */ interface Customs { diff --git a/packages/fxa-auth-server/lib/routes/newsletters.js b/packages/fxa-auth-server/lib/routes/newsletters.js index e01f54df2a..6aabeb680b 100644 --- a/packages/fxa-auth-server/lib/routes/newsletters.js +++ b/packages/fxa-auth-server/lib/routes/newsletters.js @@ -7,7 +7,7 @@ const MISC_DOCS = require('../../docs/swagger/misc-api').default; const validators = require('./validators'); const ScopeSet = require('fxa-shared/oauth/scopes').scopeSetHelpers; -const AppError = require('../../lib/error'); +const { AppError } = require('@fxa/accounts/errors'); const Joi = require('joi'); const { OAUTH_SCOPE_NEWSLETTERS } = require('fxa-shared/oauth/constants'); diff --git a/packages/fxa-auth-server/lib/routes/oauth/authorization.js b/packages/fxa-auth-server/lib/routes/oauth/authorization.js index f47319208d..66c756ad39 100644 --- a/packages/fxa-auth-server/lib/routes/oauth/authorization.js +++ b/packages/fxa-auth-server/lib/routes/oauth/authorization.js @@ -5,8 +5,8 @@ const hex = (v) => (Buffer.isBuffer(v) ? v.toString('hex') : v); const Joi = require('joi'); -const OauthError = require('../../oauth/error'); -const AuthError = require('../../error'); +const { OauthError } = require('@fxa/accounts/errors'); +const { AppError: AuthError } = require('@fxa/accounts/errors'); const validators = require('../../oauth/validators'); const { validateRequestedGrant, generateTokens } = require('../../oauth/grant'); const { makeAssertionJWT } = require('../../oauth/util'); diff --git a/packages/fxa-auth-server/lib/routes/oauth/client/get.js b/packages/fxa-auth-server/lib/routes/oauth/client/get.js index 10fb43ae85..b0edd49d0d 100644 --- a/packages/fxa-auth-server/lib/routes/oauth/client/get.js +++ b/packages/fxa-auth-server/lib/routes/oauth/client/get.js @@ -4,7 +4,7 @@ const Joi = require('joi'); -const AppError = require('../../../oauth/error'); +const { AppError } = require('@fxa/accounts/errors'); const validators = require('../../../oauth/validators'); const DESCRIPTION = require('../../../../docs/swagger/shared/descriptions').default; diff --git a/packages/fxa-auth-server/lib/routes/oauth/destroy.js b/packages/fxa-auth-server/lib/routes/oauth/destroy.js index 2b9b17ac21..ce18666606 100644 --- a/packages/fxa-auth-server/lib/routes/oauth/destroy.js +++ b/packages/fxa-auth-server/lib/routes/oauth/destroy.js @@ -5,8 +5,8 @@ const crypto = require('crypto'); const Joi = require('joi'); -const OauthError = require('../../oauth/error'); -const AuthError = require('../../error'); +const { OauthError } = require('@fxa/accounts/errors'); +const { AppError: AuthError } = require('@fxa/accounts/errors'); const encrypt = require('fxa-shared/auth/encrypt'); const validators = require('../../oauth/validators'); const { getTokenId } = require('../../oauth/token'); diff --git a/packages/fxa-auth-server/lib/routes/oauth/introspect.js b/packages/fxa-auth-server/lib/routes/oauth/introspect.js index 6ae8724f65..19bddc2a4f 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('../../oauth/error'); +const { AppError } = 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 new AppError.notPublicClient(); + throw AppError.oauthNotPublicClient(); } } } diff --git a/packages/fxa-auth-server/lib/routes/oauth/key_data.js b/packages/fxa-auth-server/lib/routes/oauth/key_data.js index 92c9f9b5d4..b9d039ba55 100644 --- a/packages/fxa-auth-server/lib/routes/oauth/key_data.js +++ b/packages/fxa-auth-server/lib/routes/oauth/key_data.js @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const Joi = require('joi'); -const OauthError = require('../../oauth/error'); -const AuthError = require('../../error'); +const { OauthError } = require('@fxa/accounts/errors'); +const { AppError: AuthError } = require('@fxa/accounts/errors'); const config = require('../../../config').default.getProperties(); const validators = require('../../oauth/validators'); const verifyAssertion = require('../../oauth/assertion'); diff --git a/packages/fxa-auth-server/lib/routes/oauth/token.js b/packages/fxa-auth-server/lib/routes/oauth/token.js index 686649fbf7..76744738a9 100644 --- a/packages/fxa-auth-server/lib/routes/oauth/token.js +++ b/packages/fxa-auth-server/lib/routes/oauth/token.js @@ -26,8 +26,8 @@ /*jshint camelcase: false*/ const crypto = require('crypto'); -const OauthError = require('../../oauth/error'); -const AuthError = require('../../error'); +const { OauthError } = require('@fxa/accounts/errors'); +const { AppError: AuthError } = require('@fxa/accounts/errors'); const buf = (v) => (Buffer.isBuffer(v) ? v : Buffer.from(v, 'hex')); const hex = (v) => (Buffer.isBuffer(v) ? v.toString('hex') : v); const Joi = require('joi'); diff --git a/packages/fxa-auth-server/lib/routes/password.ts b/packages/fxa-auth-server/lib/routes/password.ts index 102203b662..043bacf6c9 100644 --- a/packages/fxa-auth-server/lib/routes/password.ts +++ b/packages/fxa-auth-server/lib/routes/password.ts @@ -14,7 +14,7 @@ import PASSWORD_DOCS from '../../docs/swagger/password-api'; import DESCRIPTION from '../../docs/swagger/shared/descriptions'; import * as butil from '../crypto/butil'; import * as random from '../crypto/random'; -import * as error from '../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { schema as METRICS_CONTEXT_SCHEMA } from '../metrics/context'; import { gleanMetrics } from '../metrics/glean'; import * as requestHelper from '../routes/utils/request_helper'; diff --git a/packages/fxa-auth-server/lib/routes/recovery-codes.js b/packages/fxa-auth-server/lib/routes/recovery-codes.js index e57cd047a6..0eabc18501 100644 --- a/packages/fxa-auth-server/lib/routes/recovery-codes.js +++ b/packages/fxa-auth-server/lib/routes/recovery-codes.js @@ -4,7 +4,7 @@ 'use strict'; -const errors = require('../error'); +const { AppError: errors } = require('@fxa/accounts/errors'); const isA = require('joi'); const validators = require('./validators'); const { Container } = require('typedi'); diff --git a/packages/fxa-auth-server/lib/routes/recovery-key.js b/packages/fxa-auth-server/lib/routes/recovery-key.js index 3bff0d4991..9577c9f016 100644 --- a/packages/fxa-auth-server/lib/routes/recovery-key.js +++ b/packages/fxa-auth-server/lib/routes/recovery-key.js @@ -8,8 +8,7 @@ const RECOVERY_KEY_DOCS = require('../../docs/swagger/recovery-key-api').default; const DESCRIPTION = require('../../docs/swagger/shared/descriptions').default; -const AppError = require('../error'); -const errors = require('../error'); +const { AppError: errors } = require('@fxa/accounts/errors'); const { recordSecurityEvent } = require('./utils/security-event'); const validators = require('./validators'); const isA = require('joi'); @@ -149,7 +148,7 @@ module.exports = ( // if we are not explicitly requesting a key change // but an enabled key exists, throw an error } else if (enabledKey.exists && replaceKey !== true) { - throw AppError.recoveryKeyExists(); + throw errors.recoveryKeyExists(); } else { // if no key is enabled, attempt to create a new key await db.createRecoveryKey( diff --git a/packages/fxa-auth-server/lib/routes/recovery-phone.ts b/packages/fxa-auth-server/lib/routes/recovery-phone.ts index 76247d3f51..6e49c76b6e 100644 --- a/packages/fxa-auth-server/lib/routes/recovery-phone.ts +++ b/packages/fxa-auth-server/lib/routes/recovery-phone.ts @@ -27,7 +27,7 @@ import { SessionTokenAuthCredential, } from '../types'; import { E164_NUMBER } from './validators'; -import AppError from '../error'; +import { AppError } from '@fxa/accounts/errors'; import Localizer from '../l10n'; import NodeRendererBindings from '../senders/renderer/bindings-node'; import { AccountEventsManager } from '../account-events'; diff --git a/packages/fxa-auth-server/lib/routes/session.js b/packages/fxa-auth-server/lib/routes/session.js index 4c32de1d2f..faaba6d682 100644 --- a/packages/fxa-auth-server/lib/routes/session.js +++ b/packages/fxa-auth-server/lib/routes/session.js @@ -4,7 +4,7 @@ 'use strict'; -const error = require('../error'); +const { AppError: error } = require('@fxa/accounts/errors'); const isA = require('joi'); const requestHelper = require('../routes/utils/request_helper'); const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema; diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/apple.ts b/packages/fxa-auth-server/lib/routes/subscriptions/apple.ts index e4fd80a0c7..299795aa42 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/apple.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/apple.ts @@ -8,7 +8,7 @@ import { OAUTH_SCOPE_SUBSCRIPTIONS_IAP } from 'fxa-shared/oauth/constants'; import { Container } from 'typedi'; import SUBSCRIPTIONS_DOCS from '../../../docs/swagger/subscriptions-api'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { CapabilityService } from '../../payments/capability'; import { AppleIAP } from '../../payments/iap/apple-app-store/apple-iap'; import { PurchaseUpdateError } from '../../payments/iap/apple-app-store/types/errors'; diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/google.ts b/packages/fxa-auth-server/lib/routes/subscriptions/google.ts index 8a6b7d0411..bd2d19d070 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/google.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/google.ts @@ -6,7 +6,7 @@ import isA from 'joi'; import { OAUTH_SCOPE_SUBSCRIPTIONS_IAP } from 'fxa-shared/oauth/constants'; import { Container } from 'typedi'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { CapabilityService } from '../../payments/capability'; import { PlayBilling } from '../../payments/iap/google-play/play-billing'; import { PurchaseUpdateError } from '../../payments/iap/google-play/types/errors'; diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/mozilla.ts b/packages/fxa-auth-server/lib/routes/subscriptions/mozilla.ts index 6dfa48b4fc..055db95128 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/mozilla.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/mozilla.ts @@ -9,7 +9,7 @@ import { Container } from 'typedi'; import SUBSCRIPTIONS_DOCS from '../../../docs/swagger/subscriptions-api'; import { AppStoreSubscriptions } from '../../../lib/payments/iap/apple-app-store/subscriptions'; import { PlaySubscriptions } from '../../../lib/payments/iap/google-play/subscriptions'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { appStoreSubscriptionPurchaseToAppStoreSubscriptionDTO, playStoreSubscriptionPurchaseToPlayStoreSubscriptionDTO, diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/paypal-notifications.ts b/packages/fxa-auth-server/lib/routes/subscriptions/paypal-notifications.ts index 0960b4d09b..d1e7eb0e8e 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/paypal-notifications.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/paypal-notifications.ts @@ -11,7 +11,7 @@ import { import Stripe from 'stripe'; import { ConfigType } from '../../../config'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { IpnMerchPmtType, isIpnMerchPmt, diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/paypal.ts b/packages/fxa-auth-server/lib/routes/subscriptions/paypal.ts index 12174cb13e..b37eadf324 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/paypal.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/paypal.ts @@ -14,7 +14,7 @@ import { Stripe } from 'stripe'; import Container from 'typedi'; import { ConfigType } from '../../../config'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { PayPalHelper } from '../../payments/paypal/helper'; import { StripeHelper } from '../../payments/stripe'; import { reportSentryError } from '../../sentry'; diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/play-pubsub.ts b/packages/fxa-auth-server/lib/routes/subscriptions/play-pubsub.ts index fc5f630990..2e86845fc9 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/play-pubsub.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/play-pubsub.ts @@ -6,7 +6,7 @@ import isA from 'joi'; import { Container } from 'typedi'; import SUBSCRIPTIONS_DOCS from '../../../docs/swagger/subscriptions-api'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { CapabilityService } from '../../payments/capability'; import { PlayBilling } from '../../payments/iap/google-play/play-billing'; import { DeveloperNotification } from '../../payments/iap/google-play/types'; diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/stripe-webhook.ts b/packages/fxa-auth-server/lib/routes/subscriptions/stripe-webhook.ts index 568ebfb6e2..345adfb5bc 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/stripe-webhook.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/stripe-webhook.ts @@ -21,7 +21,7 @@ import { reportSentryMessage, reportValidationError, } from '../../../lib/sentry'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { PayPalHelper, RefusedError } from '../../payments/paypal'; import { RefundType } from '@fxa/payments/paypal'; import { @@ -949,7 +949,7 @@ export class StripeWebhookHandler extends StripeHandler { { acceptLanguage: account.locale, ...invoiceDetails, - email: account.primaryEmail + email: account.primaryEmail, } ); await this.stripeHelper.updateEmailSent(invoice, 'paymentFailed'); @@ -971,7 +971,7 @@ export class StripeWebhookHandler extends StripeHandler { { acceptLanguage: account.locale, ...invoiceDetails, - email: account.primaryEmail + email: account.primaryEmail, }, ]; switch (invoice.billing_reason) { @@ -1085,7 +1085,7 @@ export class StripeWebhookHandler extends StripeHandler { { acceptLanguage: account.locale, ...invoiceDetails, - email: account.primaryEmail + email: account.primaryEmail, } ); } else if (!subscription.cancel_at_period_end) { @@ -1100,7 +1100,7 @@ export class StripeWebhookHandler extends StripeHandler { ...invoiceDetails, showOutstandingBalance, cancelAtEnd: subscription.cancel_at_period_end, - email: account.primaryEmail + email: account.primaryEmail, } ); } diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/stripe.ts b/packages/fxa-auth-server/lib/routes/subscriptions/stripe.ts index e42caebbd0..44866b3e98 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/stripe.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/stripe.ts @@ -7,10 +7,7 @@ import assertNotNull from 'assert'; import isA from 'joi'; import * as Sentry from '@sentry/node'; import { SeverityLevel } from '@sentry/core'; -import { - CustomerError, - PromotionCodeManager, -} from '@fxa/payments/customer'; +import { CustomerError, PromotionCodeManager } from '@fxa/payments/customer'; import { getAccountCustomerByUid } from 'fxa-shared/db/models/auth'; import { AbbrevPlan, @@ -34,7 +31,7 @@ import { Logger } from 'mozlog'; import { Stripe } from 'stripe'; import { ConfigType } from '../../../config'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { COUNTRIES_LONG_NAME_TO_SHORT_NAME_MAP, StripeHelper, @@ -235,7 +232,7 @@ export class StripeHandler { subscriptionId ); if (!subscription) { - throw error.unknownSubscription(); + throw error.unknownSubscription(subscriptionId); } const currentPlan = singlePlan(subscription); @@ -268,8 +265,9 @@ export class StripeHandler { const { amount: planAmount, currency: planCurrency } = await this.stripeHelper.findAbbrevPlanById(planId); assertNotNull(planAmount, 'planAmount'); - if (customer && customer.currency !== planCurrency) { - throw error.currencyCurrencyMismatch(customer.currency, planCurrency); + const customerCurrency = customer?.currency || undefined; + if (customerCurrency && customerCurrency !== planCurrency) { + throw error.currencyCurrencyMismatch(customerCurrency, planCurrency); } // Update the plan @@ -359,7 +357,7 @@ export class StripeHandler { const plans = await this.stripeHelper.allAbbrevPlans(); const planForProduct = plans.find((plan) => plan.product_id === productId); if (!planForProduct) { - throw error.unknownSubscriptionPlan(); + throw error.unknownSubscriptionPlan(productId); } this.log.info('subscriptions.getProductName', { productId, diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/support.ts b/packages/fxa-auth-server/lib/routes/subscriptions/support.ts index 7056ae61db..e423fc7f54 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/support.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/support.ts @@ -7,11 +7,10 @@ import zendesk from 'node-zendesk'; import pRetry from 'p-retry'; import { ConfigType } from '../../../config'; -import error from '../../error'; +import { AppError } from '@fxa/accounts/errors'; import { AuthLogger, AuthRequest } from '../../types'; import { handleAuth } from './utils'; import { email } from '../validators'; -import AppError from '../../error'; const MISC_DOCS = require('../../../docs/swagger/misc-api').default; @@ -198,7 +197,7 @@ export const supportRoutes = ( } return { success: true, ticket: createRequest.id }; } catch (err) { - throw error.backendServiceFailure( + throw AppError.backendServiceFailure( 'zendesk', operation, { uid, email }, diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/utils.ts b/packages/fxa-auth-server/lib/routes/subscriptions/utils.ts index 4eea53d9c9..dad65e8494 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/utils.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/utils.ts @@ -5,7 +5,7 @@ import { OAUTH_SCOPE_SUBSCRIPTIONS } from 'fxa-shared/oauth/constants'; import ScopeSet from 'fxa-shared/oauth/scopes'; import { Logger } from 'mozlog'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { AuthRequest, TaxAddress } from '../../types'; /** diff --git a/packages/fxa-auth-server/lib/routes/totp.js b/packages/fxa-auth-server/lib/routes/totp.js index 37b8938a75..d7359f7da5 100644 --- a/packages/fxa-auth-server/lib/routes/totp.js +++ b/packages/fxa-auth-server/lib/routes/totp.js @@ -4,7 +4,7 @@ 'use strict'; -const errors = require('../error'); +const { AppError: errors } = require('@fxa/accounts/errors'); const validators = require('./validators'); const isA = require('joi'); const otplib = require('otplib'); diff --git a/packages/fxa-auth-server/lib/routes/utils/account.ts b/packages/fxa-auth-server/lib/routes/utils/account.ts index a3228b0ec8..aeae4d205a 100644 --- a/packages/fxa-auth-server/lib/routes/utils/account.ts +++ b/packages/fxa-auth-server/lib/routes/utils/account.ts @@ -1,6 +1,6 @@ import { StripeHelper } from '../../payments/stripe'; import { AuthLogger, AuthRequest } from '../../types'; -import error from '../../error'; +import { AppError as error } from '@fxa/accounts/errors'; import { reportSentryError } from '../../../lib/sentry'; import { RelyingPartiesQuery } from '../../../../../libs/shared/cms/src/__generated__/graphql'; import { RelyingPartyConfigurationManager } from '@fxa/shared/cms'; diff --git a/packages/fxa-auth-server/lib/routes/utils/email.js b/packages/fxa-auth-server/lib/routes/utils/email.js index 4516d36f5a..4700978ace 100644 --- a/packages/fxa-auth-server/lib/routes/utils/email.js +++ b/packages/fxa-auth-server/lib/routes/utils/email.js @@ -4,7 +4,7 @@ 'use strict'; -const error = require('../../error'); +const { AppError: error } = require('@fxa/accounts/errors'); const BOUNCE_ERRORS = new Set([ error.ERRNO.BOUNCE_COMPLAINT, diff --git a/packages/fxa-auth-server/lib/routes/utils/otp.ts b/packages/fxa-auth-server/lib/routes/utils/otp.ts index 62f4d8948c..67228d21a3 100644 --- a/packages/fxa-auth-server/lib/routes/utils/otp.ts +++ b/packages/fxa-auth-server/lib/routes/utils/otp.ts @@ -4,7 +4,7 @@ import { authenticator } from 'otplib'; import { StatsD } from 'hot-shots'; -import errors from '../../error'; +import { AppError as errors } from '@fxa/accounts/errors'; export interface OtpDb { totpToken(uid: string): Promise<{ verified: boolean; enabled: boolean }>; diff --git a/packages/fxa-auth-server/lib/routes/utils/signin.js b/packages/fxa-auth-server/lib/routes/utils/signin.js index 48f4cf4c78..b7aa9c3391 100644 --- a/packages/fxa-auth-server/lib/routes/utils/signin.js +++ b/packages/fxa-auth-server/lib/routes/utils/signin.js @@ -8,7 +8,7 @@ const emailUtils = require('./email'); const isA = require('joi'); const validators = require('../validators'); const butil = require('../../crypto/butil'); -const error = require('../../error'); +const { AppError: error } = require('@fxa/accounts/errors'); const { Container } = require('typedi'); const { AccountEventsManager } = require('../../account-events'); const { emailsMatch } = require('fxa-shared').email.helpers; diff --git a/packages/fxa-auth-server/lib/sentry.js b/packages/fxa-auth-server/lib/sentry.js index ba9955093c..00cf97748c 100644 --- a/packages/fxa-auth-server/lib/sentry.js +++ b/packages/fxa-auth-server/lib/sentry.js @@ -7,7 +7,7 @@ const Hoek = require('@hapi/hoek'); const Sentry = require('@sentry/node'); const verror = require('verror'); -const { ignoreErrors } = require('./error'); +const { ignoreErrors } = require('@fxa/accounts/errors'); const { formatMetadataValidationErrorMessage, diff --git a/packages/fxa-auth-server/lib/server.js b/packages/fxa-auth-server/lib/server.js index 96bde983d5..f9eb3db0ce 100644 --- a/packages/fxa-auth-server/lib/server.js +++ b/packages/fxa-auth-server/lib/server.js @@ -173,6 +173,7 @@ async function create(log, error, config, routes, db, statsd, glean, customs) { } const server = new Hapi.Server(serverOptions); + const oauthRoutes = routes.oauthRoutes || []; server.validator(require('joi')); server.ext('onRequest', (request, h) => { @@ -337,7 +338,7 @@ async function create(log, error, config, routes, db, statsd, glean, customs) { translateStripeErrors(response); } - response = error.translate(request, response); + response = error.translate(request, response, oauthRoutes); if (config.env !== 'prod') { response.backtrace(request.app.traced); } diff --git a/packages/fxa-auth-server/lib/tokens/bundle.js b/packages/fxa-auth-server/lib/tokens/bundle.js index 3bc7f2cc94..05ea96c93e 100644 --- a/packages/fxa-auth-server/lib/tokens/bundle.js +++ b/packages/fxa-auth-server/lib/tokens/bundle.js @@ -26,7 +26,7 @@ const butil = require('../crypto/butil'); const crypto = require('crypto'); -const error = require('../error'); +const { AppError: error } = require('@fxa/accounts/errors'); const hkdf = require('../crypto/hkdf'); const HASH_ALGORITHM = 'sha256'; diff --git a/packages/fxa-auth-server/lib/tokens/index.js b/packages/fxa-auth-server/lib/tokens/index.js index b9177600a2..9946c6dbd7 100644 --- a/packages/fxa-auth-server/lib/tokens/index.js +++ b/packages/fxa-auth-server/lib/tokens/index.js @@ -4,7 +4,7 @@ 'use strict'; -const error = require('../error'); +const { AppError: error } = require('@fxa/accounts/errors'); module.exports = (log, config) => { config = config || {}; diff --git a/packages/fxa-auth-server/scripts/recorded-future/lib.ts b/packages/fxa-auth-server/scripts/recorded-future/lib.ts index b59bbc12fb..f0fbafbac1 100644 --- a/packages/fxa-auth-server/scripts/recorded-future/lib.ts +++ b/packages/fxa-auth-server/scripts/recorded-future/lib.ts @@ -6,7 +6,7 @@ import createClient from 'openapi-fetch'; import { components, paths } from './identity-schema'; import { DB } from '../../lib/db'; -import { ERRNO } from '../../lib/error'; +import { ERRNO } from '@fxa/accounts/errors'; import * as pbkdf2 from '../../lib/crypto/pbkdf2'; import PasswordFn from '../../lib/crypto/password'; import hkdf from '../../lib/crypto/hkdf'; diff --git a/packages/fxa-auth-server/scripts/verification-reminders.js b/packages/fxa-auth-server/scripts/verification-reminders.js index 07d7698a35..e33c2c6ecb 100755 --- a/packages/fxa-auth-server/scripts/verification-reminders.js +++ b/packages/fxa-auth-server/scripts/verification-reminders.js @@ -20,7 +20,7 @@ const LIB_DIR = `${ROOT_DIR}/lib`; const config = require(`${ROOT_DIR}/config`).default.getProperties(); -const error = require(`${LIB_DIR}/error`); +const { AppError: error } = require('@fxa/accounts/errors'); const log = require(`${LIB_DIR}/log`)(config.log); const jwt = require(`${LIB_DIR}/oauth/jwt`); const verificationReminders = require(`${LIB_DIR}/verification-reminders`)( @@ -30,8 +30,9 @@ const verificationReminders = require(`${LIB_DIR}/verification-reminders`)( const Sentry = require('@sentry/node'); const cadReminders = require(`${LIB_DIR}/cad-reminders`)(config, log); -const subscriptionAccountReminders = - require(`${LIB_DIR}/subscription-account-reminders`)(log, config); +const subscriptionAccountReminders = require( + `${LIB_DIR}/subscription-account-reminders` +)(log, config); Sentry.init({}); const checkInId = Sentry.captureCheckIn({ diff --git a/packages/fxa-auth-server/test/local/account-delete.js b/packages/fxa-auth-server/test/local/account-delete.js index f2cf670db7..bbfb8e7928 100644 --- a/packages/fxa-auth-server/test/local/account-delete.js +++ b/packages/fxa-auth-server/test/local/account-delete.js @@ -9,7 +9,7 @@ const { default: Container } = require('typedi'); const { AppConfig, AuthLogger } = require('../../lib/types'); const mocks = require('../mocks'); const uuid = require('uuid'); -const error = require('../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const { AppleIAP, } = require('../../lib/payments/iap/apple-app-store/apple-iap'); diff --git a/packages/fxa-auth-server/test/local/authMethods.js b/packages/fxa-auth-server/test/local/authMethods.js index 02491e1f27..7a219fde3b 100644 --- a/packages/fxa-auth-server/test/local/authMethods.js +++ b/packages/fxa-auth-server/test/local/authMethods.js @@ -8,7 +8,7 @@ const sinon = require('sinon'); const assert = { ...sinon.assert, ...require('chai').assert }; const mocks = require('../mocks'); -const error = require('../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const authMethods = require('../../lib/authMethods'); diff --git a/packages/fxa-auth-server/test/local/bounces.js b/packages/fxa-auth-server/test/local/bounces.js index 1b08ed3752..5242d9f606 100644 --- a/packages/fxa-auth-server/test/local/bounces.js +++ b/packages/fxa-auth-server/test/local/bounces.js @@ -9,7 +9,7 @@ const ROOT_DIR = '../..'; const assert = require('assert'); const config = require(`${ROOT_DIR}/config`).default.getProperties(); const createBounces = require(`${ROOT_DIR}/lib/bounces`); -const error = require(`${ROOT_DIR}/lib/error`); +const { AppError: error } = require('@fxa/accounts/errors'); const sinon = require('sinon'); const EMAIL = Math.random() + '@example.test'; diff --git a/packages/fxa-auth-server/test/local/customs.js b/packages/fxa-auth-server/test/local/customs.js index f0cdb8440f..81068c308b 100644 --- a/packages/fxa-auth-server/test/local/customs.js +++ b/packages/fxa-auth-server/test/local/customs.js @@ -7,7 +7,7 @@ const sinon = require('sinon'); const assert = { ...sinon.assert, ...require('chai').assert }; const mocks = require('../mocks'); -const error = require(`../../lib/error.js`); +const { AppError: error } = require('@fxa/accounts/errors'); const nock = require('nock'); const CUSTOMS_URL_REAL = 'http://localhost:7000'; diff --git a/packages/fxa-auth-server/test/local/devices.js b/packages/fxa-auth-server/test/local/devices.js index 96fa42b6b5..303972cb85 100644 --- a/packages/fxa-auth-server/test/local/devices.js +++ b/packages/fxa-auth-server/test/local/devices.js @@ -9,7 +9,7 @@ const sinon = require('sinon'); const proxyquire = require('proxyquire'); const crypto = require('crypto'); const mocks = require('../mocks'); -const error = require('../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const uuid = require('uuid'); describe('lib/devices:', () => { diff --git a/packages/fxa-auth-server/test/local/email/bounce.js b/packages/fxa-auth-server/test/local/email/bounce.js index 6f579c28be..73d16de3ac 100644 --- a/packages/fxa-auth-server/test/local/email/bounce.js +++ b/packages/fxa-auth-server/test/local/email/bounce.js @@ -8,7 +8,7 @@ const ROOT_DIR = '../../..'; const { assert } = require('chai'); const bounces = require(`${ROOT_DIR}/lib/email/bounces`); -const error = require(`${ROOT_DIR}/lib/error`); +const { AppError: error } = require('@fxa/accounts/errors'); const { EventEmitter } = require('events'); const { mockLog } = require('../../mocks'); const sinon = require('sinon'); @@ -428,7 +428,7 @@ describe('bounce messages', () => { it('should log errors when deleting the email record', () => { mockDB.deleteAccount = sinon.spy(() => - Promise.reject(new error.unknownAccount('test@example.com')) + Promise.reject(error.unknownAccount('test@example.com')) ); const mockMsg = mockMessage({ bounce: { @@ -461,7 +461,7 @@ describe('bounce messages', () => { mockDB.accountRecord = sinon.spy((email) => { // Lookup only succeeds when using original, unquoted email addr. if (email !== 'test.@example.com') { - return Promise.reject(new error.unknownAccount(email)); + return Promise.reject(error.unknownAccount(email)); } return Promise.resolve({ createdAt: Date.now(), @@ -503,7 +503,7 @@ describe('bounce messages', () => { mockDB.accountRecord = sinon.spy((email) => { // Lookup only succeeds when using original, unquoted email addr. if (email !== 'test..me@example.com') { - return Promise.reject(new error.unknownAccount(email)); + return Promise.reject(error.unknownAccount(email)); } return Promise.resolve({ createdAt: Date.now(), @@ -544,7 +544,7 @@ describe('bounce messages', () => { it('should log a warning if it receives an unparseable email address', () => { mockDB.accountRecord = sinon.spy(() => - Promise.reject(new error.unknownAccount()) + Promise.reject(error.unknownAccount()) ); return mockedBounces(log, mockDB) .handleBounce( diff --git a/packages/fxa-auth-server/test/local/email/notifications.js b/packages/fxa-auth-server/test/local/email/notifications.js index 5bd1baa7df..9ecca67e20 100644 --- a/packages/fxa-auth-server/test/local/email/notifications.js +++ b/packages/fxa-auth-server/test/local/email/notifications.js @@ -7,7 +7,7 @@ const ROOT_DIR = '../../..'; const { assert } = require('chai'); -const error = require(`${ROOT_DIR}/lib/error`); +const { AppError: error } = require('@fxa/accounts/errors'); const { mockLog } = require('../../mocks'); const notifications = require(`${ROOT_DIR}/lib/email/notifications`); const sinon = require('sinon'); diff --git a/packages/fxa-auth-server/test/local/error.js b/packages/fxa-auth-server/test/local/error.js index 8a6d3d8107..f22e2f555c 100644 --- a/packages/fxa-auth-server/test/local/error.js +++ b/packages/fxa-auth-server/test/local/error.js @@ -6,15 +6,21 @@ const { assert } = require('chai'); const verror = require('verror'); -const AppError = require('../../lib/error'); -const OauthError = require('../../lib/oauth/error'); +const { AppError, OauthError } = require('@fxa/accounts/errors'); + +const mockOauthRoutes = [ + { + path: '/token', + config: { cors: true }, + }, +]; describe('AppErrors', () => { it('exported functions exist', () => { assert.equal(typeof AppError, 'function'); assert.equal(AppError.length, 4); assert.equal(typeof AppError.translate, 'function'); - assert.lengthOf(AppError.translate, 2); + assert.lengthOf(AppError.translate, 3); assert.equal(typeof AppError.invalidRequestParameter, 'function'); assert.equal(AppError.invalidRequestParameter.length, 1); assert.equal(typeof AppError.missingRequestParameter, 'function'); @@ -27,7 +33,8 @@ describe('AppErrors', () => { assert.equal(oauthError.errno, 104); const result = AppError.translate( { route: { path: '/v1/oauth/token' } }, - oauthError + oauthError, + mockOauthRoutes ); assert.ok(result instanceof AppError, 'instanceof AppError'); assert.equal(result.errno, 110); @@ -38,7 +45,8 @@ describe('AppErrors', () => { assert.equal(oauthError.errno, 104); const result = AppError.translate( { route: { path: '/v1/token' } }, - oauthError + oauthError, + mockOauthRoutes ); assert.ok(result instanceof OauthError, 'instanceof OauthError'); assert.equal(result.errno, 104); diff --git a/packages/fxa-auth-server/test/local/metrics/glean.ts b/packages/fxa-auth-server/test/local/metrics/glean.ts index e185e8ee2f..9d2ab8b6bc 100644 --- a/packages/fxa-auth-server/test/local/metrics/glean.ts +++ b/packages/fxa-auth-server/test/local/metrics/glean.ts @@ -5,7 +5,7 @@ import proxyquire from 'proxyquire'; import sinon, { SinonStub } from 'sinon'; import { assert } from 'chai'; -import AppError from '../../../lib/error'; +import { AppError } from '@fxa/accounts/errors'; import mocks from '../../mocks'; import { GleanMetricsType } from '../../../lib/metrics/glean'; import { AuthRequest } from '../../../lib/types'; @@ -192,12 +192,10 @@ const gleanProxy = proxyquire('../../../lib/metrics/glean', { recordTwoStepAuthPhoneReplaceSuccess, recordTwoStepAuthPhoneReplaceFailure: recordTwoStepAuthPhoneReplaceFailure, - recordLoginConfirmSkipForKnownIp: - recordLoginConfirmSkipForKnownIp, - recordLoginConfirmSkipForNewAccount: - recordLoginConfirmSkipForNewAccount, + recordLoginConfirmSkipForKnownIp: recordLoginConfirmSkipForKnownIp, + recordLoginConfirmSkipForNewAccount: recordLoginConfirmSkipForNewAccount, recordLoginConfirmSkipForKnownDevice: - recordLoginConfirmSkipForKnownDevice, + recordLoginConfirmSkipForKnownDevice, }), }, }); diff --git a/packages/fxa-auth-server/test/local/payments/configuration/manager.js b/packages/fxa-auth-server/test/local/payments/configuration/manager.js index d18a7998ad..8289c83010 100644 --- a/packages/fxa-auth-server/test/local/payments/configuration/manager.js +++ b/packages/fxa-auth-server/test/local/payments/configuration/manager.js @@ -26,7 +26,7 @@ const { } = require('../../../../lib/payments/configuration/manager'); const { setupFirestore } = require('../../../../lib/firestore-db'); const { randomUUID } = require('crypto'); -const errors = require('../../../../lib/error'); +const { AppError: errors } = require('@fxa/accounts/errors'); const { ProductConfig, } = require('fxa-shared/subscriptions/configuration/product'); diff --git a/packages/fxa-auth-server/test/local/payments/paypal-processor.js b/packages/fxa-auth-server/test/local/payments/paypal-processor.js index 76680a1aa7..0a420c7922 100644 --- a/packages/fxa-auth-server/test/local/payments/paypal-processor.js +++ b/packages/fxa-auth-server/test/local/payments/paypal-processor.js @@ -12,7 +12,7 @@ const { PayPalHelper } = require('../../../lib/payments/paypal/helper'); const { mockLog } = require('../../mocks'); const { PaypalProcessor } = require('../../../lib/payments/paypal/processor'); const { StripeHelper } = require('../../../lib/payments/stripe'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const paidInvoice = require('./fixtures/stripe/invoice_paid.json'); const unpaidInvoice = require('./fixtures/stripe/invoice_open.json'); const customer1 = require('./fixtures/stripe/customer1.json'); diff --git a/packages/fxa-auth-server/test/local/payments/paypal.js b/packages/fxa-auth-server/test/local/payments/paypal.js index 9580945e4f..0c906b7549 100644 --- a/packages/fxa-auth-server/test/local/payments/paypal.js +++ b/packages/fxa-auth-server/test/local/payments/paypal.js @@ -19,7 +19,7 @@ const { } = require('@fxa/payments/paypal'); const { PayPalHelper, RefusedError } = require('../../../lib/payments/paypal'); const { mockLog } = require('../../mocks'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const successfulSetExpressCheckoutResponse = require('./fixtures/paypal/set_express_checkout_success.json'); const successfulDoReferenceTransactionResponse = require('./fixtures/paypal/do_reference_transaction_success.json'); const successfulRefundTransactionResponse = require('./fixtures/paypal/refund_transaction_success.json'); @@ -736,7 +736,8 @@ describe('PayPalHelper', () => { ...validInvoice, amount_paid: 1000, }; - const expectedErrorMessage = 'Partial refunds must be less than the amount paid on the invoice'; + const expectedErrorMessage = + 'Partial refunds must be less than the amount paid on the invoice'; mockStripeHelper.getInvoicePaypalTransactionId = sinon.fake.returns('123'); mockStripeHelper.getInvoicePaypalRefundTransactionId = @@ -909,9 +910,8 @@ describe('PayPalHelper', () => { it('returns false with no billing agreement found', async () => { mockStripeHelper.getCustomerPaypalAgreement = sinon.fake.returns(undefined); - const result = await paypalHelper.conditionallyRemoveBillingAgreement( - mockCustomer - ); + const result = + await paypalHelper.conditionallyRemoveBillingAgreement(mockCustomer); assert.isFalse(result); }); @@ -921,9 +921,8 @@ describe('PayPalHelper', () => { mockCustomer.subscriptions = { data: [{ status: 'active', collection_method: 'send_invoice' }], }; - const result = await paypalHelper.conditionallyRemoveBillingAgreement( - mockCustomer - ); + const result = + await paypalHelper.conditionallyRemoveBillingAgreement(mockCustomer); assert.isFalse(result); }); @@ -933,9 +932,8 @@ describe('PayPalHelper', () => { mockCustomer.subscriptions = { data: [] }; paypalHelper.cancelBillingAgreement = sinon.fake.resolves({}); mockStripeHelper.removeCustomerPaypalAgreement = sinon.fake.resolves({}); - const result = await paypalHelper.conditionallyRemoveBillingAgreement( - mockCustomer - ); + const result = + await paypalHelper.conditionallyRemoveBillingAgreement(mockCustomer); assert.isTrue(result); sinon.assert.calledOnceWithExactly( mockStripeHelper.getCustomerPaypalAgreement, diff --git a/packages/fxa-auth-server/test/local/payments/stripe.js b/packages/fxa-auth-server/test/local/payments/stripe.js index 1bbd3b653d..fd6f3583a1 100644 --- a/packages/fxa-auth-server/test/local/payments/stripe.js +++ b/packages/fxa-auth-server/test/local/payments/stripe.js @@ -12,7 +12,7 @@ const Chance = require('chance'); const { setupAuthDatabase } = require('fxa-shared/db'); const Knex = require('knex'); const { mockLog, asyncIterable } = require('../../mocks'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const stripeError = require('stripe').Stripe.errors; const uuidv4 = require('uuid').v4; const moment = require('moment'); @@ -125,7 +125,6 @@ const { newFirestoreStripeError, StripeFirestoreMultiError, } = require('../../../lib/payments/stripe-firestore'); -const AppError = require('../../../lib/error'); const mockConfig = { authFirestore: { @@ -2034,7 +2033,7 @@ describe('#integration - StripeHelper', () => { ...expectedTemplate, valid: false, }; - const err = new AppError('previewInvoiceFailed'); + const err = new error('previewInvoiceFailed'); sandbox.stub(stripeHelper, 'previewInvoice').rejects(err); sandbox.stub(stripeHelper, 'retrievePromotionCodeForPlan').resolves({ @@ -4276,10 +4275,10 @@ describe('#integration - StripeHelper', () => { } catch (err) { thrown = err; } - assert.isObject(thrown); + assert.instanceOf(thrown, Error); assert.equal(thrown.message, 'System unavailable, try again soon'); assert.equal( - thrown.cause().message, + thrown.jse_cause?.message, 'Stripe Customer: cus_new has mismatched uid in metadata.' ); }); @@ -4589,7 +4588,7 @@ describe('#integration - StripeHelper', () => { thrown = err; } assert(stripeHelper.stripe.plans.list.calledOnce); - assert.isObject(thrown); + assert.instanceOf(thrown, Error); assert.equal(thrown.errno, error.ERRNO.UNKNOWN_SUBSCRIPTION_PLAN); }); }); diff --git a/packages/fxa-auth-server/test/local/pushbox.js b/packages/fxa-auth-server/test/local/pushbox.js index 625005d08b..9d2f01aad0 100644 --- a/packages/fxa-auth-server/test/local/pushbox.js +++ b/packages/fxa-auth-server/test/local/pushbox.js @@ -10,7 +10,7 @@ const sandbox = sinon.createSandbox(); const { pushboxApi } = require('../../lib/pushbox'); const pushboxDbModule = require('../../lib/pushbox/db'); -const error = require('../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const { mockLog } = require('../mocks'); let mockStatsD; diff --git a/packages/fxa-auth-server/test/local/routes/account.js b/packages/fxa-auth-server/test/local/routes/account.js index 90472a1aa9..c874f0ef75 100644 --- a/packages/fxa-auth-server/test/local/routes/account.js +++ b/packages/fxa-auth-server/test/local/routes/account.js @@ -17,7 +17,7 @@ const { const uuid = require('uuid'); const crypto = require('crypto'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const log = require('../../../lib/log'); const otplib = require('otplib'); const { Container } = require('typedi'); @@ -946,7 +946,7 @@ describe('/account/create', () => { wrapWrapKb: 'wibble', }, { - emailRecord: new error.unknownAccount(), + emailRecord: error.unknownAccount(), } ); const mockMailer = mocks.mockMailer(); @@ -1571,7 +1571,7 @@ describe('/account/stub', () => { wrapWrapKb: 'wibble', }, { - emailRecord: new error.unknownAccount(), + emailRecord: error.unknownAccount(), } ); const mockMailer = mocks.mockMailer(); @@ -1712,7 +1712,7 @@ describe('/account/status', () => { }, { ...(shouldError && { - emailRecord: new error.unknownAccount(), + emailRecord: error.unknownAccount(), }), } ); @@ -1891,7 +1891,7 @@ describe('/account/finish_setup', () => { verifierSetAt: options.verifierSetAt, }, { - emailRecord: new error.unknownAccount(), + emailRecord: error.unknownAccount(), } ); const mockMailer = mocks.mockMailer(); @@ -2043,7 +2043,7 @@ describe('/account/set_password', () => { verifierSetAt: options.verifierSetAt, }, { - emailRecord: new error.unknownAccount(), + emailRecord: error.unknownAccount(), } ); const mockMailer = mocks.mockMailer(); @@ -4171,10 +4171,8 @@ describe('/account/login', () => { }); it('unknown account', () => { - mockDB.accountRecord = () => - Promise.reject(new error.unknownAccount()); - mockDB.emailRecord = () => - Promise.reject(new error.unknownAccount()); + mockDB.accountRecord = () => Promise.reject(error.unknownAccount()); + mockDB.emailRecord = () => Promise.reject(error.unknownAccount()); return runTest(route, mockRequestWithUnblockCode).then( () => assert(false), (err) => { diff --git a/packages/fxa-auth-server/test/local/routes/attached-clients.js b/packages/fxa-auth-server/test/local/routes/attached-clients.js index 52ba378866..1457a67f53 100644 --- a/packages/fxa-auth-server/test/local/routes/attached-clients.js +++ b/packages/fxa-auth-server/test/local/routes/attached-clients.js @@ -9,7 +9,7 @@ const assert = { ...sinon.assert, ...require('chai').assert }; const crypto = require('crypto'); const getRoute = require('../../routes_helpers').getRoute; const mocks = require('../../mocks'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const proxyquire = require('proxyquire'); const uuid = require('uuid'); diff --git a/packages/fxa-auth-server/test/local/routes/auth-schemes/auth-oauth.js b/packages/fxa-auth-server/test/local/routes/auth-schemes/auth-oauth.js index e0c4357345..b42b79e4e7 100644 --- a/packages/fxa-auth-server/test/local/routes/auth-schemes/auth-oauth.js +++ b/packages/fxa-auth-server/test/local/routes/auth-schemes/auth-oauth.js @@ -4,8 +4,7 @@ const { assert } = require('chai'); const proxyquire = require('proxyquire'); -const AppError = require('../../../../lib/error'); -const OauthAppError = require('../../../../lib/oauth/error'); +const { AppError, OauthError: OauthAppError } = require('@fxa/accounts/errors'); const ScopeSet = require('fxa-shared').oauth.scopes; const authOauthPath = '../../../../lib/routes/auth-schemes/auth-oauth'; diff --git a/packages/fxa-auth-server/test/local/routes/auth-schemes/google-oidc.js b/packages/fxa-auth-server/test/local/routes/auth-schemes/google-oidc.js index b54fea7374..bd2f65633d 100644 --- a/packages/fxa-auth-server/test/local/routes/auth-schemes/google-oidc.js +++ b/packages/fxa-auth-server/test/local/routes/auth-schemes/google-oidc.js @@ -5,7 +5,7 @@ const { assert } = require('chai'); const sinon = require('sinon'); const proxyquire = require('proxyquire'); -const AppError = require('../../../../lib/error'); +const { AppError } = require('@fxa/accounts/errors'); let verifyIdTokenStub; const GoogleOIDCScheme = proxyquire( diff --git a/packages/fxa-auth-server/test/local/routes/auth-schemes/hawk-fxa-token.js b/packages/fxa-auth-server/test/local/routes/auth-schemes/hawk-fxa-token.js index a02e933ee3..b63337518d 100644 --- a/packages/fxa-auth-server/test/local/routes/auth-schemes/hawk-fxa-token.js +++ b/packages/fxa-auth-server/test/local/routes/auth-schemes/hawk-fxa-token.js @@ -4,7 +4,7 @@ const { assert } = require('chai'); const sinon = require('sinon'); -const AppError = require('../../../../lib/error'); +const { AppError } = require('@fxa/accounts/errors'); const { strategy, } = require('../../../../lib/routes/auth-schemes/hawk-fxa-token'); diff --git a/packages/fxa-auth-server/test/local/routes/auth-schemes/mfa.js b/packages/fxa-auth-server/test/local/routes/auth-schemes/mfa.js index 366f58a19f..0d09dc430b 100644 --- a/packages/fxa-auth-server/test/local/routes/auth-schemes/mfa.js +++ b/packages/fxa-auth-server/test/local/routes/auth-schemes/mfa.js @@ -4,7 +4,7 @@ const { assert } = require('chai'); const sinon = require('sinon'); -const AppError = require('../../../../lib/error'); +const { AppError } = require('@fxa/accounts/errors'); const { strategy } = require('../../../../lib/routes/auth-schemes/mfa'); const jwt = require('jsonwebtoken'); const uuid = require('uuid'); diff --git a/packages/fxa-auth-server/test/local/routes/auth-schemes/refresh-token.js b/packages/fxa-auth-server/test/local/routes/auth-schemes/refresh-token.js index bc14427020..0cbd621965 100644 --- a/packages/fxa-auth-server/test/local/routes/auth-schemes/refresh-token.js +++ b/packages/fxa-auth-server/test/local/routes/auth-schemes/refresh-token.js @@ -6,7 +6,7 @@ const { assert } = require('chai'); const proxyquire = require('proxyquire'); -const error = require('../../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const sinon = require('sinon'); const ScopeSet = require('fxa-shared').oauth.scopes; diff --git a/packages/fxa-auth-server/test/local/routes/auth-schemes/shared-secret.js b/packages/fxa-auth-server/test/local/routes/auth-schemes/shared-secret.js index bfd87aa632..fdf98481e7 100644 --- a/packages/fxa-auth-server/test/local/routes/auth-schemes/shared-secret.js +++ b/packages/fxa-auth-server/test/local/routes/auth-schemes/shared-secret.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const { assert } = require('chai'); -const AppError = require('../../../../lib/error'); +const { AppError } = require('@fxa/accounts/errors'); const SharedSecretScheme = require('../../../../lib/routes/auth-schemes/shared-secret'); const authStrategy = SharedSecretScheme.strategy('goodsecret')(); const noThrowStrategy = SharedSecretScheme.strategy('goodsecret', { diff --git a/packages/fxa-auth-server/test/local/routes/auth-schemes/verified-session-token.js b/packages/fxa-auth-server/test/local/routes/auth-schemes/verified-session-token.js index 5884e64689..396c644680 100644 --- a/packages/fxa-auth-server/test/local/routes/auth-schemes/verified-session-token.js +++ b/packages/fxa-auth-server/test/local/routes/auth-schemes/verified-session-token.js @@ -4,7 +4,7 @@ const { assert } = require('chai'); const sinon = require('sinon'); -const AppError = require('../../../../lib/error'); +const { AppError } = require('@fxa/accounts/errors'); const { strategy, } = require('../../../../lib/routes/auth-schemes/verified-session-token'); diff --git a/packages/fxa-auth-server/test/local/routes/devices-and-sessions.js b/packages/fxa-auth-server/test/local/routes/devices-and-sessions.js index 0210ed3c7a..875b3c330a 100644 --- a/packages/fxa-auth-server/test/local/routes/devices-and-sessions.js +++ b/packages/fxa-auth-server/test/local/routes/devices-and-sessions.js @@ -8,7 +8,7 @@ const sinon = require('sinon'); const assert = { ...sinon.assert, ...require('chai').assert }; const crypto = require('crypto'); const Joi = require('joi'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const getRoute = require('../../routes_helpers').getRoute; const mocks = require('../../mocks'); const moment = require('moment'); // Ensure consistency with production code diff --git a/packages/fxa-auth-server/test/local/routes/emails.js b/packages/fxa-auth-server/test/local/routes/emails.js index 94cba8d789..37c75f1c93 100644 --- a/packages/fxa-auth-server/test/local/routes/emails.js +++ b/packages/fxa-auth-server/test/local/routes/emails.js @@ -8,7 +8,7 @@ const sinon = require('sinon'); const assert = require('../../assert'); const crypto = require('crypto'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const getRoute = require('../../routes_helpers').getRoute; const knownIpLocation = require('../../known-ip-location'); const mocks = require('../../mocks'); diff --git a/packages/fxa-auth-server/test/local/routes/linked-accounts.js b/packages/fxa-auth-server/test/local/routes/linked-accounts.js index 49d53d6a94..ae2fda318e 100644 --- a/packages/fxa-auth-server/test/local/routes/linked-accounts.js +++ b/packages/fxa-auth-server/test/local/routes/linked-accounts.js @@ -8,7 +8,7 @@ const sinon = require('sinon'); const assert = { ...sinon.assert, ...require('chai').assert }; const getRoute = require('../../routes_helpers').getRoute; const mocks = require('../../mocks'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const proxyquire = require('proxyquire'); const glean = mocks.mockGlean(); @@ -26,11 +26,11 @@ const makeRoutes = function (options = {}, requireMocks) { const statsd = options.statsd || { increment: sinon.spy() }; const { linkedAccountRoutes } = proxyquire( - '../../../lib/routes/linked-accounts', - requireMocks || {} -); + '../../../lib/routes/linked-accounts', + requireMocks || {} + ); -return linkedAccountRoutes(log, db, config, mailer, profile, statsd, glean); + return linkedAccountRoutes(log, db, config, mailer, profile, statsd, glean); }; function runTest(route, request, assertions) { @@ -141,7 +141,7 @@ describe('/linked_account', function () { it('should exchange oauth code for `id_token` and create account', async () => { mockDB.accountRecord = sinon.spy(() => - Promise.reject(new error.unknownAccount(mockGoogleUser.email)) + Promise.reject(error.unknownAccount(mockGoogleUser.email)) ); mockRequest.payload.code = 'oauth code'; @@ -167,7 +167,7 @@ describe('/linked_account', function () { it('should create new fxa account from new google account, return session, emit Glean events', async () => { mockDB.accountRecord = sinon.spy(() => - Promise.reject(new error.unknownAccount(mockGoogleUser.email)) + Promise.reject(error.unknownAccount(mockGoogleUser.email)) ); const result = await runTest(route, mockRequest); @@ -386,7 +386,7 @@ describe('/linked_account', function () { it('should exchange oauth code for `id_token` and create account', async () => { mockDB.accountRecord = sinon.spy(() => - Promise.reject(new error.unknownAccount(mockAppleUser.email)) + Promise.reject(error.unknownAccount(mockAppleUser.email)) ); mockRequest.payload.code = 'oauth code'; @@ -415,7 +415,7 @@ describe('/linked_account', function () { it('should create new fxa account from new apple account, return session, emit Glean events', async () => { mockDB.accountRecord = sinon.spy(() => - Promise.reject(new error.unknownAccount(mockAppleUser.email)) + Promise.reject(error.unknownAccount(mockAppleUser.email)) ); const result = await runTest(route, mockRequest); @@ -575,7 +575,7 @@ describe('/linked_account', function () { try { await runTest(route, mockRequest); assert.fail('should have failed'); - } catch(err) { + } catch (err) { assert.isTrue(mockDB.deleteLinkedAccount.notCalled); assert.equal(err.errno, 138, 'unconfirmed session'); } @@ -702,7 +702,11 @@ describe('/linked_account', function () { { './utils/third-party-events': { validateSecurityToken: async () => - options.validateSecurityToken !== undefined ? (typeof options.validateSecurityToken === 'function' ? await options.validateSecurityToken() : options.validateSecurityToken) : makeJWT(), + options.validateSecurityToken !== undefined + ? typeof options.validateSecurityToken === 'function' + ? await options.validateSecurityToken() + : options.validateSecurityToken + : makeJWT(), isValidClientId: () => true, getGooglePublicKey: () => { return { @@ -888,7 +892,9 @@ describe('/linked_account', function () { setupTest({ validateSecurityToken: makeJWT('sessionRevoked') }); // Mock database to throw an error - mockDB.getLinkedAccount = sinon.stub().rejects(new Error('Database connection failed')); + mockDB.getLinkedAccount = sinon + .stub() + .rejects(new Error('Database connection failed')); // This should not throw an unhandled promise rejection await runTest(route, mockRequest); @@ -913,9 +919,12 @@ describe('/linked_account', function () { { id: 'sessionTokenId1', uid: UID, providerId: 1 }, { id: 'sessionTokenId2', uid: UID, providerId: 1 }, ]); - mockDB.deleteSessionToken = sinon.stub() - .onFirstCall().resolves() - .onSecondCall().rejects(new Error('Session deletion failed')); + mockDB.deleteSessionToken = sinon + .stub() + .onFirstCall() + .resolves() + .onSecondCall() + .rejects(new Error('Session deletion failed')); await runTest(route, mockRequest); @@ -938,7 +947,7 @@ describe('/linked_account', function () { }); }); - it('verifies statsd metrics are incremented for successful operations', async () => { + it('verifies statsd metrics are incremented for successful operations', async () => { setupTest({ validateSecurityToken: makeJWT('sessionRevoked') }); // First call - should succeed and revoke sessions @@ -983,12 +992,21 @@ describe('/linked_account', function () { // Debug: print all calls to statsd.increment // eslint-disable-next-line no-console - console.log('statsd.increment calls:', statsd.increment.getCalls().map(call => call.args)); + console.log( + 'statsd.increment calls:', + statsd.increment.getCalls().map((call) => call.args) + ); // Only these two metrics should be called, in order sinon.assert.callCount(statsd.increment, 2); - sinon.assert.calledWithExactly(statsd.increment.getCall(0), 'handleGoogleSET.received'); - sinon.assert.calledWithExactly(statsd.increment.getCall(1), 'handleGoogleSET.validationError'); + sinon.assert.calledWithExactly( + statsd.increment.getCall(0), + 'handleGoogleSET.received' + ); + sinon.assert.calledWithExactly( + statsd.increment.getCall(1), + 'handleGoogleSET.validationError' + ); // Should not call decoded or processing metrics since validation failed assert.notCalled(mockDB.getLinkedAccount); @@ -1153,7 +1171,9 @@ describe('/linked_account', function () { setupTest({ validateSecurityToken: makeJWT('consent-revoked') }); // Mock database to throw an error - mockDB.getLinkedAccount = sinon.stub().rejects(new Error('Database connection failed')); + mockDB.getLinkedAccount = sinon + .stub() + .rejects(new Error('Database connection failed')); // This should not throw an unhandled promise rejection await runTest(route, mockRequest); diff --git a/packages/fxa-auth-server/test/local/routes/mfa.js b/packages/fxa-auth-server/test/local/routes/mfa.js index 741ee712e1..3f203ed87a 100644 --- a/packages/fxa-auth-server/test/local/routes/mfa.js +++ b/packages/fxa-auth-server/test/local/routes/mfa.js @@ -10,7 +10,7 @@ const { Container } = require('typedi'); const { OtpUtils } = require('../../../lib/routes/utils/otp'); const { AccountEventsManager } = require('../../../lib/account-events'); const { strategy } = require('../../../lib/routes/auth-schemes/mfa'); -const AppError = require('../../../lib/error'); +const { AppError } = require('@fxa/accounts/errors'); describe('mfa', () => { let log, diff --git a/packages/fxa-auth-server/test/local/routes/newsletters.js b/packages/fxa-auth-server/test/local/routes/newsletters.js index 4827306e5a..55d558e71d 100644 --- a/packages/fxa-auth-server/test/local/routes/newsletters.js +++ b/packages/fxa-auth-server/test/local/routes/newsletters.js @@ -9,7 +9,7 @@ const assert = { ...sinon.assert, ...require('chai').assert }; const getRoute = require('../../routes_helpers').getRoute; const mocks = require('../../mocks'); const ScopeSet = require('fxa-shared/oauth/scopes').scopeSetHelpers; -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const { INVALID_PARAMETER, MISSING_PARAMETER } = error.ERRNO; diff --git a/packages/fxa-auth-server/test/local/routes/oauth.js b/packages/fxa-auth-server/test/local/routes/oauth.js index fe303532cd..77f339ccdd 100644 --- a/packages/fxa-auth-server/test/local/routes/oauth.js +++ b/packages/fxa-auth-server/test/local/routes/oauth.js @@ -10,7 +10,7 @@ const sinon = require('sinon'); const assert = { ...sinon.assert, ...require('chai').assert }; const getRoute = require('../../routes_helpers').getRoute; const mocks = require('../../mocks'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const JWTIdToken = require(`${ROOT_DIR}/lib/oauth/jwt_id_token`); const { OAUTH_SCOPE_OLD_SYNC } = require('fxa-shared/oauth/constants'); diff --git a/packages/fxa-auth-server/test/local/routes/password.js b/packages/fxa-auth-server/test/local/routes/password.js index 27325dc429..bbce816511 100644 --- a/packages/fxa-auth-server/test/local/routes/password.js +++ b/packages/fxa-auth-server/test/local/routes/password.js @@ -12,7 +12,7 @@ const getRoute = require('../../routes_helpers').getRoute; const uuid = require('uuid'); const crypto = require('crypto'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const log = require('../../../lib/log'); const random = require('../../../lib/crypto/random'); const glean = mocks.mockGlean(); @@ -1436,7 +1436,8 @@ describe('/password', () => { const authPWVersion2 = crypto.randomBytes(32).toString('hex'); const wrapKb = crypto.randomBytes(32).toString('hex'); const wrapKbVersion2 = crypto.randomBytes(32).toString('hex'); - const clientSalt = 'identity.mozilla.com/picl/v1/quickStretchV2:0123456789abcdef0123456789abcdef'; + const clientSalt = + 'identity.mozilla.com/picl/v1/quickStretchV2:0123456789abcdef0123456789abcdef'; const mockRequest = mocks.mockRequest({ log: mockLog, @@ -1472,7 +1473,11 @@ describe('/password', () => { customs: mockCustoms, }); - const response = await runRoute(passwordRoutes, '/mfa/password/change', mockRequest); + const response = await runRoute( + passwordRoutes, + '/mfa/password/change', + mockRequest + ); // Verify V2 credentials are handled const resetAccountCall = mockDB.resetAccount.firstCall.args[1]; @@ -1490,7 +1495,8 @@ describe('/password', () => { const authPWVersion2 = crypto.randomBytes(32).toString('hex'); const wrapKb = crypto.randomBytes(32).toString('hex'); const wrapKbVersion2 = crypto.randomBytes(32).toString('hex'); - const clientSalt = 'identity.mozilla.com/picl/v1/quickStretchV2:0123456789abcdef0123456789abcdef'; + const clientSalt = + 'identity.mozilla.com/picl/v1/quickStretchV2:0123456789abcdef0123456789abcdef'; mockDB.account = sinon.spy(() => ({ uid, @@ -1502,7 +1508,9 @@ describe('/password', () => { })); // Mock signinUtils.checkPassword to return true for upgrade scenario - mockDB.checkPassword = sinon.spy(() => Promise.resolve({ v1: true, v2: false })); + mockDB.checkPassword = sinon.spy(() => + Promise.resolve({ v1: true, v2: false }) + ); const passwordRoutes = makeRoutes({ db: mockDB, @@ -1538,7 +1546,11 @@ describe('/password', () => { query: {}, }); - const response = await runRoute(passwordRoutes, '/mfa/password/change', mockRequest); + const response = await runRoute( + passwordRoutes, + '/mfa/password/change', + mockRequest + ); // Verify upgrade scenario is handled const resetAccountCall = mockDB.resetAccount.firstCall; diff --git a/packages/fxa-auth-server/test/local/routes/recovery-codes.js b/packages/fxa-auth-server/test/local/routes/recovery-codes.js index e09afbd6cc..fcc124d2ce 100644 --- a/packages/fxa-auth-server/test/local/routes/recovery-codes.js +++ b/packages/fxa-auth-server/test/local/routes/recovery-codes.js @@ -8,7 +8,7 @@ const sinon = require('sinon'); const assert = { ...sinon.assert, ...require('chai').assert }; const getRoute = require('../../routes_helpers').getRoute; const mocks = require('../../mocks'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const { Container } = require('typedi'); const { BackupCodeManager } = require('@fxa/accounts/two-factor'); const { AccountEventsManager } = require('../../../lib/account-events'); diff --git a/packages/fxa-auth-server/test/local/routes/recovery-keys.js b/packages/fxa-auth-server/test/local/routes/recovery-keys.js index 3e77d7ed2e..093156aab7 100644 --- a/packages/fxa-auth-server/test/local/routes/recovery-keys.js +++ b/packages/fxa-auth-server/test/local/routes/recovery-keys.js @@ -9,7 +9,7 @@ const sinon = require('sinon'); const { assert } = require('chai'); const getRoute = require('../../routes_helpers').getRoute; const mocks = require('../../mocks'); -const errors = require('../../../lib/error'); +const { AppError: errors } = require('@fxa/accounts/errors'); const proxyquire = require('proxyquire'); const { OAUTH_SCOPE_OLD_SYNC } = require('fxa-shared/oauth/constants'); diff --git a/packages/fxa-auth-server/test/local/routes/recovery-phone.js b/packages/fxa-auth-server/test/local/routes/recovery-phone.js index 3c2c584bac..349e043ca3 100644 --- a/packages/fxa-auth-server/test/local/routes/recovery-phone.js +++ b/packages/fxa-auth-server/test/local/routes/recovery-phone.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const { AccountEventsManager } = require('../../../lib/account-events'); -const AppError = require('../../../lib/error'); +const { AppError } = require('@fxa/accounts/errors'); const chai = require('chai'); const { AccountManager } = require('@fxa/shared/account/account'); diff --git a/packages/fxa-auth-server/test/local/routes/session.js b/packages/fxa-auth-server/test/local/routes/session.js index 361a60bed0..96042b42b8 100644 --- a/packages/fxa-auth-server/test/local/routes/session.js +++ b/packages/fxa-auth-server/test/local/routes/session.js @@ -8,7 +8,7 @@ const crypto = require('crypto'); const getRoute = require('../../routes_helpers').getRoute; const knownIpLocation = require('../../known-ip-location'); const mocks = require('../../mocks'); -const error = require('../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const sinon = require('sinon'); const otplib = require('otplib'); const assert = require('../../assert'); diff --git a/packages/fxa-auth-server/test/local/routes/subscriptions/apple.js b/packages/fxa-auth-server/test/local/routes/subscriptions/apple.js index 86a87903c3..72fbfac209 100644 --- a/packages/fxa-auth-server/test/local/routes/subscriptions/apple.js +++ b/packages/fxa-auth-server/test/local/routes/subscriptions/apple.js @@ -15,7 +15,7 @@ const { const { PurchaseUpdateError, } = require('../../../../lib/payments/iap/apple-app-store/types/errors'); -const error = require('../../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const { AuthLogger } = require('../../../../lib/types'); const { AppleIAP, @@ -75,9 +75,8 @@ describe('AppleIapHandler', () => { registerToUserAccount: sinon.fake.resolves({}), }; iapConfig.getBundleId = sinon.fake.resolves('testPackage'); - const result = await appleIapHandler.registerOriginalTransactionId( - request - ); + const result = + await appleIapHandler.registerOriginalTransactionId(request); assert.calledOnce(appleIap.purchaseManager.registerToUserAccount); assert.calledOnce(iapConfig.getBundleId); assert.calledOnce(mockCapabilityService.iapUpdate); diff --git a/packages/fxa-auth-server/test/local/routes/subscriptions/google.js b/packages/fxa-auth-server/test/local/routes/subscriptions/google.js index e568d879ef..efe4c1044a 100644 --- a/packages/fxa-auth-server/test/local/routes/subscriptions/google.js +++ b/packages/fxa-auth-server/test/local/routes/subscriptions/google.js @@ -16,7 +16,7 @@ const { const { PurchaseUpdateError, } = require('../../../../lib/payments/iap/google-play/types/errors'); -const error = require('../../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const { AuthLogger } = require('../../../../lib/types'); const { PlayBilling } = require('../../../../lib/payments/iap/google-play'); const { IAPConfig } = require('../../../../lib/payments/iap/iap-config'); diff --git a/packages/fxa-auth-server/test/local/routes/subscriptions/mozilla.js b/packages/fxa-auth-server/test/local/routes/subscriptions/mozilla.js index 5e5ee75d4c..990d42d72b 100644 --- a/packages/fxa-auth-server/test/local/routes/subscriptions/mozilla.js +++ b/packages/fxa-auth-server/test/local/routes/subscriptions/mozilla.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const { MozillaSubscriptionTypes } = require('fxa-shared/subscriptions/types'); -const { ERRNO } = require('../../../../lib/error'); +const { ERRNO } = require('@fxa/accounts/errors'); ('use strict'); diff --git a/packages/fxa-auth-server/test/local/routes/subscriptions/paypal-notifications.js b/packages/fxa-auth-server/test/local/routes/subscriptions/paypal-notifications.js index d6bdf39ecf..6daf8080ea 100644 --- a/packages/fxa-auth-server/test/local/routes/subscriptions/paypal-notifications.js +++ b/packages/fxa-auth-server/test/local/routes/subscriptions/paypal-notifications.js @@ -11,7 +11,7 @@ const { Container } = require('typedi'); const mocks = require('../../../mocks'); -const error = require('../../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const completedMerchantPaymentNotification = require('../fixtures/merch_pmt_completed.json'); const pendingMerchantPaymentNotification = require('../fixtures/merch_pmt_pending.json'); const billingAgreementCancelNotification = require('../fixtures/mp_cancel_successful.json'); diff --git a/packages/fxa-auth-server/test/local/routes/subscriptions/paypal.js b/packages/fxa-auth-server/test/local/routes/subscriptions/paypal.js index 7ba731a3b3..314d46d78b 100644 --- a/packages/fxa-auth-server/test/local/routes/subscriptions/paypal.js +++ b/packages/fxa-auth-server/test/local/routes/subscriptions/paypal.js @@ -10,7 +10,7 @@ const { Container } = require('typedi'); const assert = { ...sinon.assert, ...require('chai').assert }; const { filterCustomer } = require('fxa-shared/subscriptions/stripe'); -const error = require('../../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const { getRoute } = require('../../../routes_helpers'); const mocks = require('../../../mocks'); const { PayPalHelper } = require('../../../../lib/payments/paypal/helper'); diff --git a/packages/fxa-auth-server/test/local/routes/subscriptions/stripe-webhooks.js b/packages/fxa-auth-server/test/local/routes/subscriptions/stripe-webhooks.js index ee972d397d..75b2bda6fb 100644 --- a/packages/fxa-auth-server/test/local/routes/subscriptions/stripe-webhooks.js +++ b/packages/fxa-auth-server/test/local/routes/subscriptions/stripe-webhooks.js @@ -8,7 +8,7 @@ const sinon = require('sinon'); const assert = { ...sinon.assert, ...require('chai').assert }; const uuid = require('uuid'); const mocks = require('../../../mocks'); -const error = require('../../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const Sentry = require('@sentry/node'); const sentryModule = require('../../../../lib/sentry'); const { @@ -2175,7 +2175,7 @@ describe('StripeWebhookHandler', () => { { acceptLanguage: mockAccount.locale, ...mockInvoiceDetails, - email: mockAccount.primaryEmail + email: mockAccount.primaryEmail, } ); }); @@ -2213,7 +2213,7 @@ describe('StripeWebhookHandler', () => { { acceptLanguage: mockAccount.locale, ...mockInvoiceDetails, - email: mockAccount.primaryEmail + email: mockAccount.primaryEmail, } ); if (expectedMethodName === 'sendSubscriptionFirstInvoiceEmail') { @@ -2225,7 +2225,7 @@ describe('StripeWebhookHandler', () => { { acceptLanguage: mockAccount.locale, ...mockInvoiceDetails, - email: mockAccount.primaryEmail + email: mockAccount.primaryEmail, } ); } else { @@ -2414,7 +2414,7 @@ describe('StripeWebhookHandler', () => { { acceptLanguage: mockAccount.locale, ...mockInvoiceDetails, - email: mockAccount.primaryEmail + email: mockAccount.primaryEmail, } ); } else { @@ -2455,7 +2455,7 @@ describe('StripeWebhookHandler', () => { ...mockInvoiceDetails, showOutstandingBalance: options.hasOutstandingBalance, cancelAtEnd: subscription.cancel_at_period_end, - email: mockAccount.primaryEmail + email: mockAccount.primaryEmail, } ); } else { diff --git a/packages/fxa-auth-server/test/local/routes/subscriptions/stripe.js b/packages/fxa-auth-server/test/local/routes/subscriptions/stripe.js index 115c4327fe..e082cb2b6a 100644 --- a/packages/fxa-auth-server/test/local/routes/subscriptions/stripe.js +++ b/packages/fxa-auth-server/test/local/routes/subscriptions/stripe.js @@ -9,7 +9,7 @@ const assert = require('chai').assert; const { Container } = require('typedi'); const uuid = require('uuid'); const mocks = require('../../../mocks'); -const error = require('../../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const Sentry = require('@sentry/node'); const sentryModule = require('../../../../lib/sentry'); const { @@ -21,7 +21,6 @@ const { PromotionCodeManager, CustomerError, } = require('@fxa/payments/customer'); -const WError = require('verror').WError; const uuidv4 = require('uuid').v4; const proxyquire = require('proxyquire').noPreserveCache(); const dbStub = { @@ -337,7 +336,7 @@ describe('handleAuth', () => { return handleAuth(db, INVALID_AUTH).then( () => Promise.reject(new Error('Method expected to reject')), (err) => { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.message, 'Requested scopes are not allowed'); } ); @@ -811,7 +810,7 @@ describe('DirectStripeRoutes', () => { await directStripeRoutesInstance.previewInvoice(request); assert.fail('Preview Invoice should fail'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.INVALID_INVOICE_PREVIEW_REQUEST); } }); @@ -824,7 +823,7 @@ describe('DirectStripeRoutes', () => { await directStripeRoutesInstance.previewInvoice(request); assert.fail('Preview Invoice should fail'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.UNSUPPORTED_LOCATION); assert.equal( err.message, @@ -1046,7 +1045,7 @@ describe('DirectStripeRoutes', () => { ); assert.fail('Unknown customer'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.UNKNOWN_SUBSCRIPTION_CUSTOMER); } }); @@ -1091,7 +1090,7 @@ describe('DirectStripeRoutes', () => { VALID_REQUEST ); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal( err.errno, error.ERRNO.SUBSCRIPTION_PROMO_CODE_NOT_APPLIED @@ -1314,7 +1313,7 @@ describe('DirectStripeRoutes', () => { await directStripeRoutesInstance.createSubscriptionWithPMI(request); assert.fail('Create subscription should fail'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.UNSUPPORTED_LOCATION); assert.equal( err.message, @@ -1335,7 +1334,7 @@ describe('DirectStripeRoutes', () => { ); assert.fail('Create subscription without a customer should fail.'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.UNKNOWN_SUBSCRIPTION_CUSTOMER); } }); @@ -1355,7 +1354,7 @@ describe('DirectStripeRoutes', () => { ); assert.fail('Create subscription when already subscribed should fail.'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.SUBSCRIPTION_ALREADY_EXISTS); sinon.assert.notCalled( directStripeRoutesInstance.stripeHelper.cancelSubscription @@ -1377,7 +1376,7 @@ describe('DirectStripeRoutes', () => { ); assert.fail('Create subscription with wrong planCurrency should fail.'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.INVALID_REGION); assert.equal( err.message, @@ -1402,7 +1401,7 @@ describe('DirectStripeRoutes', () => { ); assert.fail('Create subscription with wrong planCurrency should fail.'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.INVALID_REGION); assert.equal( err.message, @@ -1432,7 +1431,7 @@ describe('DirectStripeRoutes', () => { assert.fail('Create subscription with wrong planCurrency should fail.'); } catch (err) { assert.equal(deleteAccountIfUnverifiedStub.calledOnce, true); - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.INVALID_REGION); } }); @@ -1458,7 +1457,7 @@ describe('DirectStripeRoutes', () => { assert.fail('Create subscription with wrong planCurrency should fail.'); } catch (err) { assert.equal(deleteAccountIfUnverifiedStub.calledOnce, true); - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.INVALID_REGION); } }); @@ -1486,7 +1485,7 @@ describe('DirectStripeRoutes', () => { assert.fail('Create subscription with wrong planCurrency should fail.'); } catch (err) { assert.equal(deleteAccountIfUnverifiedStub.calledOnce, true); - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.INVALID_REGION); } }); @@ -1799,7 +1798,7 @@ describe('DirectStripeRoutes', () => { await directStripeRoutesInstance.retryInvoice(VALID_REQUEST); assert.fail('Create customer should fail.'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.UNKNOWN_SUBSCRIPTION_CUSTOMER); } }); @@ -1830,7 +1829,7 @@ describe('DirectStripeRoutes', () => { await directStripeRoutesInstance.createSetupIntent(VALID_REQUEST); assert.fail('Create customer should fail.'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.UNKNOWN_SUBSCRIPTION_CUSTOMER); } }); @@ -1912,7 +1911,7 @@ describe('DirectStripeRoutes', () => { 'Update default payment method with new payment method country that does not match customer currency should fail.' ); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.INVALID_REGION); assert.equal( err.message, @@ -1929,7 +1928,7 @@ describe('DirectStripeRoutes', () => { ); assert.fail('Create customer should fail.'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.UNKNOWN_SUBSCRIPTION_CUSTOMER); } }); @@ -2110,7 +2109,7 @@ describe('DirectStripeRoutes', () => { 'Detaching a payment method from a non-existent customer should fail.' ); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.UNKNOWN_SUBSCRIPTION_CUSTOMER); } }); @@ -2267,7 +2266,7 @@ describe('DirectStripeRoutes', () => { await directStripeRoutesInstance.updateSubscription(VALID_REQUEST); assert.fail('Update subscription with invalid plan should fail.'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.INVALID_PLAN_UPDATE); assert.equal(err.message, 'Subscription plan is not a valid update'); } @@ -2288,7 +2287,7 @@ describe('DirectStripeRoutes', () => { 'Update subscription with wrong plan currency should fail.' ); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.INVALID_CURRENCY); assert.equal(err.message, 'Changing currencies is not permitted.'); } @@ -2300,7 +2299,7 @@ describe('DirectStripeRoutes', () => { await directStripeRoutesInstance.updateSubscription(VALID_REQUEST); assert.fail('Method expected to reject'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.UNKNOWN_SUBSCRIPTION); assert.equal(err.message, 'Unknown subscription'); } @@ -2329,7 +2328,7 @@ describe('DirectStripeRoutes', () => { }); assert.fail('Getting a product name should fail.'); } catch (err) { - assert.instanceOf(err, WError); + assert.instanceOf(err, error); assert.equal(err.errno, error.ERRNO.UNKNOWN_SUBSCRIPTION_PLAN); } }); diff --git a/packages/fxa-auth-server/test/local/routes/support.js b/packages/fxa-auth-server/test/local/routes/support.js index 3a0f301a74..ee2b8c02a5 100644 --- a/packages/fxa-auth-server/test/local/routes/support.js +++ b/packages/fxa-auth-server/test/local/routes/support.js @@ -11,7 +11,7 @@ const getRoute = require('../../routes_helpers').getRoute; const mocks = require('../../mocks'); const nock = require('nock'); const { supportRoutes } = require('../../../lib/routes/subscriptions/support'); -const AppError = require('../../../lib/error'); +const { AppError } = require('@fxa/accounts/errors'); let config, log, diff --git a/packages/fxa-auth-server/test/local/routes/totp.js b/packages/fxa-auth-server/test/local/routes/totp.js index f7d199c01f..b4154bc207 100644 --- a/packages/fxa-auth-server/test/local/routes/totp.js +++ b/packages/fxa-auth-server/test/local/routes/totp.js @@ -12,7 +12,7 @@ const otplib = require('otplib'); const { Container } = require('typedi'); const crypto = require('crypto'); const { AccountEventsManager } = require('../../../lib/account-events'); -const authErrors = require('../../../lib/error'); +const { AppError: authErrors } = require('@fxa/accounts/errors'); const { RecoveryPhoneService } = require('@fxa/accounts/recovery-phone'); const { BackupCodeManager } = require('@fxa/accounts/two-factor'); diff --git a/packages/fxa-auth-server/test/local/routes/utils/signin.js b/packages/fxa-auth-server/test/local/routes/utils/signin.js index 04e6cdecbb..21e78ae6e8 100644 --- a/packages/fxa-auth-server/test/local/routes/utils/signin.js +++ b/packages/fxa-auth-server/test/local/routes/utils/signin.js @@ -10,7 +10,7 @@ const { Container } = require('typedi'); const mocks = require('../../../mocks'); const Password = require('../../../../lib/crypto/password')({}, {}); -const error = require('../../../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const butil = require('../../../../lib/crypto/butil'); const otpUtils = require('../../../../lib/routes/utils/otp').default( {}, diff --git a/packages/fxa-auth-server/test/local/senders/emails.ts b/packages/fxa-auth-server/test/local/senders/emails.ts index e89172b8ec..0d8169e036 100644 --- a/packages/fxa-auth-server/test/local/senders/emails.ts +++ b/packages/fxa-auth-server/test/local/senders/emails.ts @@ -18,8 +18,7 @@ import { MOCK_DEVICE_OS, MOCK_DEVICE_OS_VERSION, } from '../../../lib/senders/emails/partials/userDevice/mocks'; -import AppError from '../../../lib/error'; -import { AUTH_SERVER_ERRNOS } from 'fxa-shared/lib/errors'; +import { AppError, ERRNO as AUTH_SERVER_ERRNOS } from '@fxa/accounts/errors'; import { Container } from 'typedi'; import { ProductConfigurationManager } from '../../../../../libs/shared/cms/src'; diff --git a/packages/fxa-auth-server/test/local/sentry.js b/packages/fxa-auth-server/test/local/sentry.js index 31a8eb5650..62a430c331 100644 --- a/packages/fxa-auth-server/test/local/sentry.js +++ b/packages/fxa-auth-server/test/local/sentry.js @@ -10,7 +10,7 @@ const sinon = require('sinon'); const verror = require('verror'); const Hapi = require('@hapi/hapi'); const Sentry = require('@sentry/node'); -const error = require('../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const config = require('../../config').default.getProperties(); const { @@ -92,11 +92,9 @@ describe('Sentry', () => { it('adds payload details to an internal validation error', async () => { await configureSentry(server, config); - const err = error.internalValidationError( - 'internalError', - { extra: 'data' }, - 'Missing data' - ); + const err = error.internalValidationError('internalError', { + extra: 'data', + }); await server.events.emit( { name: 'request', @@ -106,15 +104,18 @@ describe('Sentry', () => { [{}, { error: err }] ); - sandbox.assert.calledTwice(scopeSpy.setContext); - sandbox.assert.calledWith(scopeSpy.setContext, 'payload', { - code: 500, - errno: 998, - error: 'Internal Server Error', - info: 'https://mozilla.github.io/ecosystem-platform/api#section/Response-format', - message: 'An internal validation check failed.', - op: 'internalError', - }); + sandbox.assert.calledWithMatch( + scopeSpy.setContext, + 'payload', + sinon.match({ + code: 500, + errno: error.ERRNO.INTERNAL_VALIDATION_ERROR, + error: 'Internal Server Error', + info: 'https://mozilla.github.io/ecosystem-platform/api#section/Response-format', + message: 'An internal validation check failed.', + op: 'internalError', + }) + ); sandbox.assert.calledWith(scopeSpy.setContext, 'payload.data', { extra: 'data', }); diff --git a/packages/fxa-auth-server/test/local/server.js b/packages/fxa-auth-server/test/local/server.js index de1751716c..01869a4023 100644 --- a/packages/fxa-auth-server/test/local/server.js +++ b/packages/fxa-auth-server/test/local/server.js @@ -8,13 +8,12 @@ const ROOT_DIR = '../..'; const { assert } = require('chai'); const EndpointError = require('poolee/lib/error')(require('util').inherits); -const error = require(`${ROOT_DIR}/lib/error`); +const { AppError: error } = require('@fxa/accounts/errors'); const knownIpLocation = require('../known-ip-location'); const mocks = require('../mocks'); const proxyquire = require('proxyquire'); const sinon = require('sinon'); const { Account } = require('fxa-shared/db/models/auth/account'); -const AppError = require('../../lib/error'); const glean = mocks.mockGlean(); const customs = mocks.mockCustoms(); @@ -594,6 +593,8 @@ describe('lib/server', () => { error: 'Request blocked', info: 'https://mozilla.github.io/ecosystem-platform/api#section/Response-format', message: 'The request was blocked for security reasons', + retryAfter: undefined, + retryAfterLocalized: undefined, }; beforeEach(() => { glean.registration.error.reset(); @@ -730,7 +731,7 @@ describe('lib/server', () => { it('handles customs block', async () => { customs.checkIpOnly = sinon.spy(async () => { - throw new AppError.tooManyRequests(100, 'foo'); + throw error.tooManyRequests(100, 'foo'); }); const { statusCode, result } = await query('/account/status'); @@ -756,7 +757,7 @@ describe('lib/server', () => { ]) { it('will skip ' + endpoint, async () => { customs.checkIpOnly = sinon.spy(async () => { - throw new AppError.tooManyRequests(100, 'foo'); + throw error.tooManyRequests(100, 'foo'); }); await query(endpoint); assert.equal(customs.checkIpOnly.callCount, 0); diff --git a/packages/fxa-auth-server/test/mocks.js b/packages/fxa-auth-server/test/mocks.js index 7767f0d1ed..b4fe74e670 100644 --- a/packages/fxa-auth-server/test/mocks.js +++ b/packages/fxa-auth-server/test/mocks.js @@ -11,7 +11,7 @@ const assert = require('assert'); const config = require('../config').default.getProperties(); const crypto = require('crypto'); -const error = require('../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const knownIpLocation = require('./known-ip-location'); const sinon = require('sinon'); const { normalizeEmail } = require('fxa-shared').email.helpers; diff --git a/packages/fxa-auth-server/test/oauth/grant.js b/packages/fxa-auth-server/test/oauth/grant.js index 519d7d450b..5c62bad630 100644 --- a/packages/fxa-auth-server/test/oauth/grant.js +++ b/packages/fxa-auth-server/test/oauth/grant.js @@ -9,7 +9,7 @@ const { default: Container } = require('typedi'); const { config } = require('../../config'); const ScopeSet = require('fxa-shared').oauth.scopes; -const AppError = require('../../lib/oauth/error'); +const { OauthError: AppError } = require('@fxa/accounts/errors'); const { decodeJWT } = require('../lib/util'); const { CapabilityService } = require('../../lib/payments/capability'); diff --git a/packages/fxa-auth-server/test/oauth/jwt_access_token.js b/packages/fxa-auth-server/test/oauth/jwt_access_token.js index 0a9a2fa80d..33745c3adf 100644 --- a/packages/fxa-auth-server/test/oauth/jwt_access_token.js +++ b/packages/fxa-auth-server/test/oauth/jwt_access_token.js @@ -5,7 +5,7 @@ const { assert } = require('chai'); const sinon = require('sinon'); const proxyquire = require('proxyquire'); -const AppError = require('../../lib/oauth/error'); +const { OauthError: AppError } = require('@fxa/accounts/errors'); const ScopeSet = require('fxa-shared').oauth.scopes; const { OAUTH_SCOPE_OLD_SYNC } = require('fxa-shared/oauth/constants'); const TOKEN_SERVER_URL = diff --git a/packages/fxa-auth-server/test/oauth/jwt_id_token.js b/packages/fxa-auth-server/test/oauth/jwt_id_token.js index 5b7a7f354b..2598ae1d06 100644 --- a/packages/fxa-auth-server/test/oauth/jwt_id_token.js +++ b/packages/fxa-auth-server/test/oauth/jwt_id_token.js @@ -5,7 +5,7 @@ const { assert } = require('chai'); const jsonwebtoken = require('jsonwebtoken'); -const AppError = require('../../lib/oauth/error'); +const { OauthError: AppError } = require('@fxa/accounts/errors'); const { config } = require('../../config'); const JWTIdToken = require('../../lib/oauth/jwt_id_token'); const { diff --git a/packages/fxa-auth-server/test/remote/account_destroy_tests.js b/packages/fxa-auth-server/test/remote/account_destroy_tests.js index 175885c621..7ac4905adc 100644 --- a/packages/fxa-auth-server/test/remote/account_destroy_tests.js +++ b/packages/fxa-auth-server/test/remote/account_destroy_tests.js @@ -9,7 +9,7 @@ const TestServer = require('../test_server'); const Client = require('../client')(); const otplib = require('otplib'); const crypto = require('crypto'); -const AppError = require('../../lib/error'); +const { AppError } = require('@fxa/accounts/errors'); const config = require('../../config').default.getProperties(); diff --git a/packages/fxa-auth-server/test/remote/oauth_session_token_scope_tests.js b/packages/fxa-auth-server/test/remote/oauth_session_token_scope_tests.js index 5e48e15378..2de24dc865 100644 --- a/packages/fxa-auth-server/test/remote/oauth_session_token_scope_tests.js +++ b/packages/fxa-auth-server/test/remote/oauth_session_token_scope_tests.js @@ -12,7 +12,7 @@ const { OAUTH_SCOPE_SESSION_TOKEN, OAUTH_SCOPE_OLD_SYNC, } = require('fxa-shared/oauth/constants'); -const error = require('../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const testUtils = require('../lib/util'); const OAUTH_CLIENT_NAME = 'Android Components Reference Browser'; @@ -20,143 +20,144 @@ const PUBLIC_CLIENT_ID = '3c49430b43dfba77'; const MOCK_CODE_VERIFIER = 'abababababababababababababababababababababa'; const MOCK_CODE_CHALLENGE = 'YPhkZqm08uTfwjNSiYcx80-NPT9Zn94kHboQW97KyV0'; -[{version:""},{version:"V2"}].forEach((testOptions) => { +[{ version: '' }, { version: 'V2' }].forEach((testOptions) => { + describe(`#integration${testOptions.version} - /oauth/ session token scope`, function () { + this.timeout(60000); + let client; + let email; + let password; + let server; -describe(`#integration${testOptions.version} - /oauth/ session token scope`, function () { - this.timeout(60000); - let client; - let email; - let password; - let server; - - before(async () => { - testUtils.disableLogs(); - server = await TestServer.start(config, false); - }); - - after(async () => { - await TestServer.stop(server); - testUtils.restoreStdoutWrite(); - }); - - beforeEach(async () => { - email = server.uniqueEmail(); - password = 'test password'; - client = await Client.createAndVerify( - config.publicUrl, - email, - password, - server.mailbox, - testOptions - ); - }); - - it('provides a session token using the session token scope', async () => { - const SCOPE = OAUTH_SCOPE_SESSION_TOKEN; - const res = await client.createAuthorizationCode({ - client_id: PUBLIC_CLIENT_ID, - scope: SCOPE, - state: 'xyz', - code_challenge: MOCK_CODE_CHALLENGE, - code_challenge_method: 'S256', - }); - assert.ok(res.redirect); - assert.ok(res.code); - assert.equal(res.state, 'xyz'); - - const tokenRes = await client.grantOAuthTokens({ - client_id: PUBLIC_CLIENT_ID, - code: res.code, - code_verifier: MOCK_CODE_VERIFIER, - }); - assert.ok(tokenRes.access_token); - assert.ok(tokenRes.session_token); - assert.notEqual(tokenRes.session_token, client.sessionToken); - assert.notOk(tokenRes.session_token_id); - assert.equal(tokenRes.scope, SCOPE); - assert.ok(tokenRes.auth_at); - assert.ok(tokenRes.expires_in); - assert.ok(tokenRes.token_type); - }); - - it('works with oldsync and session token scopes', async () => { - const SCOPE = `${OAUTH_SCOPE_SESSION_TOKEN} ${OAUTH_SCOPE_OLD_SYNC}`; - const res = await client.createAuthorizationCode({ - client_id: PUBLIC_CLIENT_ID, - scope: SCOPE, - state: 'xyz', - code_challenge: MOCK_CODE_CHALLENGE, - code_challenge_method: 'S256', - access_type: 'offline', + before(async () => { + testUtils.disableLogs(); + server = await TestServer.start(config, false); }); - const tokenRes = await client.grantOAuthTokens({ - client_id: PUBLIC_CLIENT_ID, - code: res.code, - code_verifier: MOCK_CODE_VERIFIER, - }); - assert.ok(tokenRes.access_token); - assert.ok(tokenRes.session_token); - assert.ok(tokenRes.refresh_token); - // added a new device - const allClients = await client.attachedClients(); - assert.equal(allClients.length, 2); - assert.ok(allClients[0].sessionTokenId); - assert.equal(allClients[0].name, OAUTH_CLIENT_NAME); - assert.ok(allClients[1].sessionTokenId); - // the 'isCurrentSession' should be attached to the original device - // and not the newly created device entry - assert.isFalse( - allClients[0].isCurrentSession, - 'session is not on the new device' - ); - assert.isTrue( - allClients[1].isCurrentSession, - 'session is still the original device' - ); - assert.notEqual(allClients[0].sessionTokenId, allClients[1].sessionTokenId); - }); - - it('rejects invalid sessionToken', async () => { - const res = await client.createAuthorizationCode({ - client_id: PUBLIC_CLIENT_ID, - scope: OAUTH_SCOPE_SESSION_TOKEN, - state: 'xyz', - code_challenge: MOCK_CODE_CHALLENGE, - code_challenge_method: 'S256', + after(async () => { + await TestServer.stop(server); + testUtils.restoreStdoutWrite(); }); - await client.destroySession(); - try { - await client.grantOAuthTokens({ + beforeEach(async () => { + email = server.uniqueEmail(); + password = 'test password'; + client = await Client.createAndVerify( + config.publicUrl, + email, + password, + server.mailbox, + testOptions + ); + }); + + it('provides a session token using the session token scope', async () => { + const SCOPE = OAUTH_SCOPE_SESSION_TOKEN; + const res = await client.createAuthorizationCode({ + client_id: PUBLIC_CLIENT_ID, + scope: SCOPE, + state: 'xyz', + code_challenge: MOCK_CODE_CHALLENGE, + code_challenge_method: 'S256', + }); + assert.ok(res.redirect); + assert.ok(res.code); + assert.equal(res.state, 'xyz'); + + const tokenRes = await client.grantOAuthTokens({ client_id: PUBLIC_CLIENT_ID, code: res.code, code_verifier: MOCK_CODE_VERIFIER, }); - assert.fail('should have thrown'); - } catch (err) { - assert.equal(err.errno, error.ERRNO.UNKNOWN_AUTHORIZATION_CODE); - } - }); - - it('contains no token when scopes is not set', async () => { - const res = await client.createAuthorizationCode({ - client_id: PUBLIC_CLIENT_ID, - scope: 'profile', - state: 'xyz', - code_challenge: MOCK_CODE_CHALLENGE, - code_challenge_method: 'S256', + assert.ok(tokenRes.access_token); + assert.ok(tokenRes.session_token); + assert.notEqual(tokenRes.session_token, client.sessionToken); + assert.notOk(tokenRes.session_token_id); + assert.equal(tokenRes.scope, SCOPE); + assert.ok(tokenRes.auth_at); + assert.ok(tokenRes.expires_in); + assert.ok(tokenRes.token_type); }); - const tokenRes = await client.grantOAuthTokens({ - client_id: PUBLIC_CLIENT_ID, - code: res.code, - code_verifier: MOCK_CODE_VERIFIER, + it('works with oldsync and session token scopes', async () => { + const SCOPE = `${OAUTH_SCOPE_SESSION_TOKEN} ${OAUTH_SCOPE_OLD_SYNC}`; + const res = await client.createAuthorizationCode({ + client_id: PUBLIC_CLIENT_ID, + scope: SCOPE, + state: 'xyz', + code_challenge: MOCK_CODE_CHALLENGE, + code_challenge_method: 'S256', + access_type: 'offline', + }); + + const tokenRes = await client.grantOAuthTokens({ + client_id: PUBLIC_CLIENT_ID, + code: res.code, + code_verifier: MOCK_CODE_VERIFIER, + }); + assert.ok(tokenRes.access_token); + assert.ok(tokenRes.session_token); + assert.ok(tokenRes.refresh_token); + // added a new device + const allClients = await client.attachedClients(); + assert.equal(allClients.length, 2); + assert.ok(allClients[0].sessionTokenId); + assert.equal(allClients[0].name, OAUTH_CLIENT_NAME); + assert.ok(allClients[1].sessionTokenId); + // the 'isCurrentSession' should be attached to the original device + // and not the newly created device entry + assert.isFalse( + allClients[0].isCurrentSession, + 'session is not on the new device' + ); + assert.isTrue( + allClients[1].isCurrentSession, + 'session is still the original device' + ); + assert.notEqual( + allClients[0].sessionTokenId, + allClients[1].sessionTokenId + ); + }); + + it('rejects invalid sessionToken', async () => { + const res = await client.createAuthorizationCode({ + client_id: PUBLIC_CLIENT_ID, + scope: OAUTH_SCOPE_SESSION_TOKEN, + state: 'xyz', + code_challenge: MOCK_CODE_CHALLENGE, + code_challenge_method: 'S256', + }); + + await client.destroySession(); + try { + await client.grantOAuthTokens({ + client_id: PUBLIC_CLIENT_ID, + code: res.code, + code_verifier: MOCK_CODE_VERIFIER, + }); + assert.fail('should have thrown'); + } catch (err) { + assert.equal(err.errno, error.ERRNO.UNKNOWN_AUTHORIZATION_CODE); + } + }); + + it('contains no token when scopes is not set', async () => { + const res = await client.createAuthorizationCode({ + client_id: PUBLIC_CLIENT_ID, + scope: 'profile', + state: 'xyz', + code_challenge: MOCK_CODE_CHALLENGE, + code_challenge_method: 'S256', + }); + + const tokenRes = await client.grantOAuthTokens({ + client_id: PUBLIC_CLIENT_ID, + code: res.code, + code_verifier: MOCK_CODE_VERIFIER, + }); + assert.ok(tokenRes.access_token); + assert.notOk(tokenRes.session_token); + assert.notOk(tokenRes.session_token_id); }); - assert.ok(tokenRes.access_token); - assert.notOk(tokenRes.session_token); - assert.notOk(tokenRes.session_token_id); }); }); - -}); diff --git a/packages/fxa-auth-server/test/remote/oauth_tests.js b/packages/fxa-auth-server/test/remote/oauth_tests.js index 80e350f1ed..07d9f629d6 100644 --- a/packages/fxa-auth-server/test/remote/oauth_tests.js +++ b/packages/fxa-auth-server/test/remote/oauth_tests.js @@ -9,7 +9,7 @@ const TestServer = require('../test_server'); const Client = require('../client')(); const config = require('../../config').default.getProperties(); const { OAUTH_SCOPE_OLD_SYNC } = require('fxa-shared/oauth/constants'); -const error = require('../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const testUtils = require('../lib/util'); const PUBLIC_CLIENT_ID = '3c49430b43dfba77'; diff --git a/packages/fxa-auth-server/test/remote/recovery_email_emails.js b/packages/fxa-auth-server/test/remote/recovery_email_emails.js index 48e25159c7..4196c72ccc 100644 --- a/packages/fxa-auth-server/test/remote/recovery_email_emails.js +++ b/packages/fxa-auth-server/test/remote/recovery_email_emails.js @@ -7,7 +7,7 @@ const { assert } = require('chai'); const TestServer = require('../test_server'); const Client = require('../client')(); -const ERRNO = require('../../lib/error').ERRNO; +const ERRNO = require('@fxa/accounts/errors').ERRNO; const { setupAccountDatabase } = require('@fxa/shared/db/mysql/account'); const cfg = require('../../config').default.getProperties(); const { email: emailHelper } = require('fxa-shared'); diff --git a/packages/fxa-auth-server/test/remote/subscription_tests.js b/packages/fxa-auth-server/test/remote/subscription_tests.js index a0ef43193e..f1d226ad80 100644 --- a/packages/fxa-auth-server/test/remote/subscription_tests.js +++ b/packages/fxa-auth-server/test/remote/subscription_tests.js @@ -11,7 +11,7 @@ const { default: Container } = require('typedi'); const { OAUTH_SCOPE_SUBSCRIPTIONS } = require('fxa-shared/oauth/constants'); const clientFactory = require('../client')(); const config = require(`${ROOT_DIR}/config`).default.getProperties(); -const error = require(`${ROOT_DIR}/lib/error`); +const { AppError: error } = require('@fxa/accounts/errors'); const testServerFactory = require('../test_server'); const { CapabilityService } = require('../../lib/payments/capability'); const { StripeHelper } = require('../../lib/payments/stripe'); diff --git a/packages/fxa-auth-server/test/remote/token_code_tests.js b/packages/fxa-auth-server/test/remote/token_code_tests.js index a4ec518e47..49bf533d9e 100644 --- a/packages/fxa-auth-server/test/remote/token_code_tests.js +++ b/packages/fxa-auth-server/test/remote/token_code_tests.js @@ -8,7 +8,7 @@ const { assert } = require('chai'); const config = require('../../config').default.getProperties(); const TestServer = require('../test_server'); const Client = require('../client')(); -const error = require('../../lib/error'); +const { AppError: error } = require('@fxa/accounts/errors'); const { default: Container } = require('typedi'); const { PlaySubscriptions, @@ -17,232 +17,240 @@ const { AppStoreSubscriptions, } = require('../../lib/payments/iap/apple-app-store/subscriptions'); -[{version:""},{version:"V2"}].forEach((testOptions) => { +[{ version: '' }, { version: 'V2' }].forEach((testOptions) => { + describe(`#integration${testOptions.version} - remote tokenCodes`, function () { + this.timeout(60000); -describe(`#integration${testOptions.version} - remote tokenCodes`, function () { - this.timeout(60000); + let server, client, email, code; + const password = 'pssssst'; - let server, client, email, code; - const password = 'pssssst'; - - before(async () => { - Container.set(PlaySubscriptions, {}); - Container.set(AppStoreSubscriptions, {}); - server = await TestServer.start(config); - }); - - after(async () => { - await TestServer.stop(server); - }); - - beforeEach(() => { - email = server.uniqueEmail('@mozilla.com'); - return Client.createAndVerify( - config.publicUrl, - email, - password, - server.mailbox, - testOptions - ).then((x) => { - client = x; - assert.ok(client.authAt, 'authAt was set'); - }); - }); - - it('should error with invalid code', () => { - return Client.login(config.publicUrl, email, password, { - ...testOptions, - verificationMethod: 'email-2fa', - keys: true, - }) - .then((res) => { - client = res; - assert.equal( - res.verificationMethod, - 'email-2fa', - 'sets correct verification method' - ); - return client.verifyShortCodeEmail('011001'); - }) - .then( - () => { - assert.fail('consumed invalid code'); - }, - (err) => { - assert.equal( - err.errno, - error.ERRNO.INVALID_EXPIRED_OTP_CODE, - 'correct errno' - ); - return client.emailStatus(); - } - ) - .then((status) => { - assert.equal(status.verified, false, 'account is not verified'); - assert.equal(status.emailVerified, true, 'email is verified'); - assert.equal(status.sessionVerified, false, 'session is not verified'); - }); - }); - - it('should error with invalid request param when using wrong code format', () => { - return Client.login(config.publicUrl, email, password, { - ...testOptions, - verificationMethod: 'email-2fa', - keys: true, - }) - .then((res) => { - client = res; - assert.equal( - res.verificationMethod, - 'email-2fa', - 'sets correct verification method' - ); - return client.verifyShortCodeEmail('Cool Runnings 4 u'); - }) - .then( - () => { - assert.fail('consumed invalid code'); - }, - (err) => { - assert.equal( - err.errno, - error.ERRNO.INVALID_PARAMETER, - 'correct errno' - ); - return client.emailStatus(); - } - ) - .then((status) => { - assert.equal(status.verified, false, 'account is not verified'); - assert.equal(status.emailVerified, true, 'email is verified'); - assert.equal(status.sessionVerified, false, 'session is not verified'); - }); - }); - - it('should consume valid code', () => { - return Client.login(config.publicUrl, email, password, { - ...testOptions, - verificationMethod: 'email-2fa', - keys: true, - }) - .then((res) => { - client = res; - assert.equal( - res.verificationMethod, - 'email-2fa', - 'sets correct verification method' - ); - return client.emailStatus(); - }) - .then((status) => { - assert.equal(status.verified, false, 'account is not verified'); - assert.equal(status.emailVerified, true, 'email is verified'); - assert.equal(status.sessionVerified, false, 'session is not verified'); - return server.mailbox.waitForEmail(email); - }) - .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'verifyLoginCode'); - code = emailData.headers['x-signin-verify-code']; - assert.ok(code, 'code is sent'); - return client.verifyShortCodeEmail(code); - }) - .then((res) => { - assert.ok(res, 'verified successful response'); - return client.emailStatus(); - }) - .then((status) => { - assert.equal(status.verified, true, 'account is verified'); - assert.equal(status.emailVerified, true, 'email is verified'); - assert.equal(status.sessionVerified, true, 'session is verified'); - }); - }); - - it('should accept optional uid parameter in request body', () => { - return Client.login(config.publicUrl, email, password, { - ...testOptions, - verificationMethod: 'email-2fa', - keys: true, - }) - .then((res) => { - client = res; - return server.mailbox.waitForEmail(email); - }) - .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'verifyLoginCode'); - code = emailData.headers['x-signin-verify-code']; - assert.ok(code, 'code is sent'); - return client.verifyShortCodeEmail(code, { uid: client.uid }); - }) - .then((res) => { - assert.ok(res, 'verified successful response'); - return client.emailStatus(); - }) - .then((status) => { - assert.equal(status.verified, true, 'account is verified'); - assert.equal(status.emailVerified, true, 'email is verified'); - assert.equal(status.sessionVerified, true, 'session is verified'); - }); - }); - - it('should retrieve account keys', () => { - return Client.login(config.publicUrl, email, password, { - ...testOptions, - verificationMethod: 'email-2fa', - keys: true, - }) - .then((res) => { - client = res; - return server.mailbox.waitForEmail(email); - }) - .then((emailData) => { - assert.equal(emailData.headers['x-template-name'], 'verifyLoginCode'); - code = emailData.headers['x-signin-verify-code']; - assert.ok(code, 'code is sent'); - return client.verifyShortCodeEmail(code); - }) - .then((res) => { - assert.ok(res, 'verified successful response'); - - return client.keys(); - }) - .then((keys) => { - assert.ok(keys.kA, 'has kA keys'); - assert.ok(keys.kB, 'has kB keys'); - assert.ok(keys.wrapKb, 'has wrapKb keys'); - }); - }); - - it('should resend authentication code', async () => { - await Client.login(config.publicUrl, email, password, { - ...testOptions, - verificationMethod: 'email-2fa', - keys: true, + before(async () => { + Container.set(PlaySubscriptions, {}); + Container.set(AppStoreSubscriptions, {}); + server = await TestServer.start(config); }); - let emailData = await server.mailbox.waitForEmail(email); - const originalMessageId = emailData['messageId']; - const originalCode = emailData.headers['x-verify-short-code']; + after(async () => { + await TestServer.stop(server); + }); - assert.equal(emailData.headers['x-template-name'], 'verifyLoginCode'); + beforeEach(() => { + email = server.uniqueEmail('@mozilla.com'); + return Client.createAndVerify( + config.publicUrl, + email, + password, + server.mailbox, + testOptions + ).then((x) => { + client = x; + assert.ok(client.authAt, 'authAt was set'); + }); + }); - await client.resendVerifyShortCodeEmail(); + it('should error with invalid code', () => { + return Client.login(config.publicUrl, email, password, { + ...testOptions, + verificationMethod: 'email-2fa', + keys: true, + }) + .then((res) => { + client = res; + assert.equal( + res.verificationMethod, + 'email-2fa', + 'sets correct verification method' + ); + return client.verifyShortCodeEmail('011001'); + }) + .then( + () => { + assert.fail('consumed invalid code'); + }, + (err) => { + assert.equal( + err.errno, + error.ERRNO.INVALID_EXPIRED_OTP_CODE, + 'correct errno' + ); + return client.emailStatus(); + } + ) + .then((status) => { + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal( + status.sessionVerified, + false, + 'session is not verified' + ); + }); + }); - emailData = await server.mailbox.waitForEmail(email); - assert.equal(emailData.headers['x-template-name'], 'verifyLoginCode'); + it('should error with invalid request param when using wrong code format', () => { + return Client.login(config.publicUrl, email, password, { + ...testOptions, + verificationMethod: 'email-2fa', + keys: true, + }) + .then((res) => { + client = res; + assert.equal( + res.verificationMethod, + 'email-2fa', + 'sets correct verification method' + ); + return client.verifyShortCodeEmail('Cool Runnings 4 u'); + }) + .then( + () => { + assert.fail('consumed invalid code'); + }, + (err) => { + assert.equal( + err.errno, + error.ERRNO.INVALID_PARAMETER, + 'correct errno' + ); + return client.emailStatus(); + } + ) + .then((status) => { + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal( + status.sessionVerified, + false, + 'session is not verified' + ); + }); + }); - assert.notEqual( - originalMessageId, - emailData['messageId'], - 'different email was sent' - ); - assert.equal( - originalCode, - emailData.headers['x-verify-short-code'], - 'codes match' - ); + it('should consume valid code', () => { + return Client.login(config.publicUrl, email, password, { + ...testOptions, + verificationMethod: 'email-2fa', + keys: true, + }) + .then((res) => { + client = res; + assert.equal( + res.verificationMethod, + 'email-2fa', + 'sets correct verification method' + ); + return client.emailStatus(); + }) + .then((status) => { + assert.equal(status.verified, false, 'account is not verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal( + status.sessionVerified, + false, + 'session is not verified' + ); + return server.mailbox.waitForEmail(email); + }) + .then((emailData) => { + assert.equal(emailData.headers['x-template-name'], 'verifyLoginCode'); + code = emailData.headers['x-signin-verify-code']; + assert.ok(code, 'code is sent'); + return client.verifyShortCodeEmail(code); + }) + .then((res) => { + assert.ok(res, 'verified successful response'); + return client.emailStatus(); + }) + .then((status) => { + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, true, 'session is verified'); + }); + }); + + it('should accept optional uid parameter in request body', () => { + return Client.login(config.publicUrl, email, password, { + ...testOptions, + verificationMethod: 'email-2fa', + keys: true, + }) + .then((res) => { + client = res; + return server.mailbox.waitForEmail(email); + }) + .then((emailData) => { + assert.equal(emailData.headers['x-template-name'], 'verifyLoginCode'); + code = emailData.headers['x-signin-verify-code']; + assert.ok(code, 'code is sent'); + return client.verifyShortCodeEmail(code, { uid: client.uid }); + }) + .then((res) => { + assert.ok(res, 'verified successful response'); + return client.emailStatus(); + }) + .then((status) => { + assert.equal(status.verified, true, 'account is verified'); + assert.equal(status.emailVerified, true, 'email is verified'); + assert.equal(status.sessionVerified, true, 'session is verified'); + }); + }); + + it('should retrieve account keys', () => { + return Client.login(config.publicUrl, email, password, { + ...testOptions, + verificationMethod: 'email-2fa', + keys: true, + }) + .then((res) => { + client = res; + return server.mailbox.waitForEmail(email); + }) + .then((emailData) => { + assert.equal(emailData.headers['x-template-name'], 'verifyLoginCode'); + code = emailData.headers['x-signin-verify-code']; + assert.ok(code, 'code is sent'); + return client.verifyShortCodeEmail(code); + }) + .then((res) => { + assert.ok(res, 'verified successful response'); + + return client.keys(); + }) + .then((keys) => { + assert.ok(keys.kA, 'has kA keys'); + assert.ok(keys.kB, 'has kB keys'); + assert.ok(keys.wrapKb, 'has wrapKb keys'); + }); + }); + + it('should resend authentication code', async () => { + await Client.login(config.publicUrl, email, password, { + ...testOptions, + verificationMethod: 'email-2fa', + keys: true, + }); + + let emailData = await server.mailbox.waitForEmail(email); + const originalMessageId = emailData['messageId']; + const originalCode = emailData.headers['x-verify-short-code']; + + assert.equal(emailData.headers['x-template-name'], 'verifyLoginCode'); + + await client.resendVerifyShortCodeEmail(); + + emailData = await server.mailbox.waitForEmail(email); + assert.equal(emailData.headers['x-template-name'], 'verifyLoginCode'); + + assert.notEqual( + originalMessageId, + emailData['messageId'], + 'different email was sent' + ); + assert.equal( + originalCode, + emailData.headers['x-verify-short-code'], + 'codes match' + ); + }); }); - - -}); - }); diff --git a/packages/fxa-auth-server/test/scripts/delete-unverified-accounts.ts b/packages/fxa-auth-server/test/scripts/delete-unverified-accounts.ts index 25f841df36..f8f8cc331f 100644 --- a/packages/fxa-auth-server/test/scripts/delete-unverified-accounts.ts +++ b/packages/fxa-auth-server/test/scripts/delete-unverified-accounts.ts @@ -15,6 +15,8 @@ const execOptions = { env: { ...process.env, NODE_ENV: 'dev', + STRIPE_API_KEY: 'sk_test_dummy', + SUBHUB_STRIPE_APIKEY: 'sk_test_dummy', }, }; diff --git a/packages/fxa-auth-server/test/scripts/recorded-future/lib.ts b/packages/fxa-auth-server/test/scripts/recorded-future/lib.ts index 397f1c226c..71dd3ab14f 100644 --- a/packages/fxa-auth-server/test/scripts/recorded-future/lib.ts +++ b/packages/fxa-auth-server/test/scripts/recorded-future/lib.ts @@ -7,7 +7,7 @@ import sinon from 'sinon'; import * as lib from '../../../scripts/recorded-future/lib'; import { SearchResultIdentity } from '../../../scripts/recorded-future/lib'; -import AppError, { ERRNO } from '../../../lib/error'; +import { AppError, ERRNO } from '@fxa/accounts/errors'; describe('Recorded Future credentials search and reset script lib', () => { const payload = { domain: 'login.example.com', limit: 10 }; diff --git a/packages/fxa-settings/package.json b/packages/fxa-settings/package.json index 33872f573d..3321dc8185 100644 --- a/packages/fxa-settings/package.json +++ b/packages/fxa-settings/package.json @@ -80,7 +80,8 @@ "\\.(css|sass|scss)$": "identity-obj-proxy", "@fxa/shared/l10n": "/../../libs/shared/l10n/src/index.ts", "@fxa/shared/metrics/glean": "/../../libs/shared/metrics/glean/src/index.ts", - "^@fxa/shared/assets(.*)$": "/../../libs/shared/assets/src$1" + "^@fxa/shared/assets(.*)$": "/../../libs/shared/assets/src$1", + "@fxa/accounts/errors": "/../../libs/accounts/errors/src/index.ts" }, "moduleFileExtensions": [ "web.js", diff --git a/packages/fxa-settings/src/lib/auth-errors/auth-errors.ts b/packages/fxa-settings/src/lib/auth-errors/auth-errors.ts index c3c77139ef..5fb4c74237 100644 --- a/packages/fxa-settings/src/lib/auth-errors/auth-errors.ts +++ b/packages/fxa-settings/src/lib/auth-errors/auth-errors.ts @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { AuthServerError } from 'fxa-auth-client/browser'; +import { ERRNO } from '@fxa/accounts/errors'; export type AuthUiError = AuthServerError & { version?: number }; @@ -15,334 +16,334 @@ const REUSED_SINGLE_USE_CONFIRMATION_CODE_ERROR_MESSAGE = // We add a `version` value onto the errors for translations. This allows us to signal to translators (via the string ID) that a string has been updated. const ERRORS = { ACCOUNT_ALREADY_EXISTS: { - errno: 101, + errno: ERRNO.ACCOUNT_EXISTS, message: 'Account already exists', }, UNKNOWN_ACCOUNT: { - errno: 102, + errno: ERRNO.ACCOUNT_UNKNOWN, message: 'Unknown account', }, INCORRECT_PASSWORD: { - errno: 103, + errno: ERRNO.INCORRECT_PASSWORD, message: 'Incorrect password', }, UNVERIFIED_ACCOUNT: { - errno: 104, + errno: ERRNO.ACCOUNT_UNVERIFIED, message: 'Unconfirmed account', version: 2, }, INVALID_VERIFICATION_CODE: { - errno: 105, + errno: ERRNO.INVALID_VERIFICATION_CODE, message: 'Invalid confirmation code', version: 2, }, INVALID_JSON: { - errno: 106, + errno: ERRNO.INVALID_JSON, message: 'Invalid JSON in request body', }, /* INVALID_PARAMETER: { - errno: 107, + errno: ERRNO.INVALID_PARAMETER, message: 'Invalid parameter: %(param)s' }, MISSING_PARAMETER: { - errno: 108, + errno: ERRNO.MISSING_PARAMETER, message: 'Missing parameter: %(param)s' }, */ INVALID_REQUEST_SIGNATURE: { - errno: 109, + errno: ERRNO.INVALID_REQUEST_SIGNATURE, message: 'Invalid request signature', }, INVALID_TOKEN: { - errno: 110, + errno: ERRNO.INVALID_TOKEN, message: 'Invalid token', }, INVALID_TIMESTAMP: { - errno: 111, + errno: ERRNO.INVALID_TIMESTAMP, message: 'Invalid timestamp in request signature', }, MISSING_CONTENT_LENGTH_HEADER: { - errno: 112, + errno: ERRNO.MISSING_CONTENT_LENGTH_HEADER, message: 'Missing content-length header', }, REQUEST_TOO_LARGE: { - errno: 113, + errno: ERRNO.REQUEST_TOO_LARGE, message: 'Request body too large', }, THROTTLED: { - errno: 114, + errno: ERRNO.THROTTLED, message: "You've tried too many times. Try again later.", }, INVALID_NONCE: { - errno: 115, + errno: ERRNO.INVALID_NONCE, message: 'Invalid nonce in request signature', }, ENDPOINT_NOT_SUPPORTED: { - errno: 116, + errno: ERRNO.ENDPOINT_NOT_SUPPORTED, message: 'This endpoint is no longer supported', }, INCORRECT_EMAIL_CASE: { - errno: 120, + errno: ERRNO.INCORRECT_EMAIL_CASE, message: 'Incorrect email case', }, /* ACCOUNT_LOCKED: { - errno: 121, + errno: ERRNO.ACCOUNT_LOCKED, message: 'Your account has been locked for security reasons') }, ACCOUNT_NOT_LOCKED: { - errno: 122, + errno: ERRNO.ACCOUNT_NOT_LOCKED, message: UNEXPECTED_ERROR_MESSAGE }, */ REQUEST_BLOCKED: { - errno: 125, + errno: ERRNO.REQUEST_BLOCKED, message: 'The request was blocked for security reasons', }, ACCOUNT_RESET: { - errno: 126, + errno: ERRNO.ACCOUNT_RESET, message: 'Your account has been locked for security reasons', }, INCORRECT_UNBLOCK_CODE: { - errno: 127, + errno: ERRNO.INVALID_UNBLOCK_CODE, message: 'Invalid authorization code', }, INVALID_PHONE_NUMBER: { - errno: 129, + errno: ERRNO.INVALID_PHONE_NUMBER, message: 'You entered an invalid phone number. Please check it and try again.', version: 2, }, INVALID_PHONE_REGION: { - errno: 130, + errno: ERRNO.INVALID_REGION, message: 'Cannot send to this country', }, - // Important! errno: 131 has been deprecated. This is no longer a valid errno and should not be reused. - // Important! errno: 132 has been deprecated. This is no longer a valid errno and should not be reused. + // Important! errno: ERRNO.INVALID_MESSAGE_ID has been deprecated. This is no longer a valid errno and should not be reused. + // Important! errno: ERRNO.MESSAGE_REJECTED has been deprecated. This is no longer a valid errno and should not be reused. EMAIL_SENT_COMPLAINT: { - errno: 133, + errno: ERRNO.BOUNCE_COMPLAINT, message: 'Your email was just returned', }, EMAIL_HARD_BOUNCE: { - errno: 134, + errno: ERRNO.BOUNCE_HARD, message: 'Your email was just returned. Mistyped email?', }, EMAIL_SOFT_BOUNCE: { - errno: 135, + errno: ERRNO.BOUNCE_SOFT, message: 'Unable to deliver email', }, EMAIL_EXISTS: { - errno: 136, + errno: ERRNO.EMAIL_EXISTS, message: 'This email was already confirmed by another user', version: 2, }, UNVERIFIED_SESSION: { - errno: 138, + errno: ERRNO.SESSION_UNVERIFIED, message: 'Unconfirmed session', version: 2, }, EMAIL_PRIMARY_EXISTS: { - errno: 139, + errno: ERRNO.USER_PRIMARY_EMAIL_EXISTS, message: 'Secondary email must be different than your account email', }, EMAIL_VERIFIED_PRIMARY_EXISTS: { - errno: 140, + errno: ERRNO.VERIFIED_PRIMARY_EMAIL_EXISTS, message: 'Account already exists', }, MAX_SECONDARY_EMAILS_REACHED: { - errno: 188, + errno: ERRNO.MAX_SECONDARY_EMAILS_REACHED, message: 'You have reached the maximum allowed secondary emails', }, ACCOUNT_OWNS_EMAIL: { - errno: 189, + errno: ERRNO.ACCOUNT_OWNS_EMAIL, message: 'This email already exists on your account', }, UNVERIFIED_PRIMARY_EMAIL_NEWLY_CREATED: { - errno: 141, + errno: ERRNO.UNVERIFIED_PRIMARY_EMAIL_NEWLY_CREATED, message: 'Account already exists', }, LOGIN_WITH_SECONDARY_EMAIL: { - errno: 142, + errno: ERRNO.LOGIN_WITH_SECONDARY_EMAIL, message: 'Primary account email required for sign-in', }, VERIFIED_SECONDARY_EMAIL_EXISTS: { - errno: 144, + errno: ERRNO.VERIFIED_SECONDARY_EMAIL_EXISTS, message: 'This email is reserved by another account. Try again later or use a different email address.', }, RESET_PASSWORD_WITH_SECONDARY_EMAIL: { - errno: 145, + errno: ERRNO.RESET_PASSWORD_WITH_SECONDARY_EMAIL, message: 'Primary account email required for reset', }, INVALID_SIGNIN_CODE: { - errno: 146, + errno: ERRNO.INVALID_SIGNIN_CODE, message: 'Invalid signin code', }, CHANGE_EMAIL_TO_UNVERIFIED_EMAIL: { - errno: 147, + errno: ERRNO.CHANGE_EMAIL_TO_UNVERIFIED_EMAIL, message: 'Can not change primary email to an unconfirmed email', version: 2, }, CHANGE_EMAIL_TO_UNOWNED_EMAIL: { - errno: 148, + errno: ERRNO.CHANGE_EMAIL_TO_UNOWNED_EMAIL, message: 'Can not change primary email to an email that does not belong to this account', }, LOGIN_WITH_INVALID_EMAIL: { - errno: 149, + errno: ERRNO.LOGIN_WITH_INVALID_EMAIL, message: 'This email can not currently be used to login', }, RESEND_EMAIL_CODE_TO_UNOWNED_EMAIL: { - errno: 150, + errno: ERRNO.RESEND_EMAIL_CODE_TO_UNOWNED_EMAIL, message: 'Can not resend email code to an email that does not belong to this account', version: 2, }, FAILED_TO_SEND_EMAIL: { - errno: 151, + errno: ERRNO.FAILED_TO_SEND_EMAIL, message: 'Failed to send email', }, INVALID_OTP_CODE: { - errno: 152, + errno: ERRNO.INVALID_TOKEN_VERIFICATION_CODE, message: 'Valid code required', }, EXPIRED_TOKEN_VERIFICATION_CODE: { - errno: 153, + errno: ERRNO.EXPIRED_TOKEN_VERIFICATION_CODE, message: 'This confirmation code has expired', version: 2, }, TOTP_TOKEN_EXISTS: { - errno: 154, + errno: ERRNO.TOTP_TOKEN_EXISTS, message: 'A TOTP token already exists for this account', }, TOTP_TOKEN_NOT_FOUND: { - errno: 155, + errno: ERRNO.TOTP_TOKEN_NOT_FOUND, message: 'TOTP token not found', }, RECOVERY_CODE_NOT_FOUND: { - errno: 156, + errno: ERRNO.RECOVERY_CODE_NOT_FOUND, message: 'Backup authentication code not found', }, DEVICE_COMMAND_UNAVAILABLE: { - errno: 157, + errno: ERRNO.DEVICE_COMMAND_UNAVAILABLE, message: 'Unavailable device command', }, RECOVERY_KEY_NOT_FOUND: { - errno: 158, + errno: ERRNO.RECOVERY_KEY_NOT_FOUND, message: 'Account recovery key not found', }, INVALID_RECOVERY_KEY: { - errno: 159, + errno: ERRNO.RECOVERY_KEY_INVALID, message: 'Invalid account recovery key', }, TOTP_REQUIRED: { - errno: 160, + errno: ERRNO.TOTP_REQUIRED, message: 'This request requires two step authentication enabled on your account.', }, RECOVERY_KEY_ALREADY_EXISTS: { - errno: 161, + errno: ERRNO.RECOVERY_KEY_EXISTS, message: 'Account recovery key already exists.', }, REDIS_CONFLICT: { - errno: 165, + errno: ERRNO.REDIS_CONFLICT, message: 'Failed due to a conflicting request, please try again.', }, INSUFFICIENT_ACR_VALUES: { - errno: 170, + errno: ERRNO.INSUFFICIENT_ACR_VALUES, message: 'This request requires two step authentication enabled on your account.', }, UNKNOWN_SUBSCRIPTION_CUSTOMER: { - errno: 176, + errno: ERRNO.UNKNOWN_SUBSCRIPTION_CUSTOMER, message: 'Unknown customer for subscription.', }, UNKNOWN_SUBSCRIPTION: { - errno: 177, + errno: ERRNO.UNKNOWN_SUBSCRIPTION, message: 'Unknown subscription.', }, UNKNOWN_SUBSCRIPTION_PLAN: { - errno: 178, + errno: ERRNO.UNKNOWN_SUBSCRIPTION_PLAN, message: 'Unknown plan for subscription.', }, REJECTED_SUBSCRIPTION_PAYMENT_TOKEN: { - errno: 179, + errno: ERRNO.REJECTED_SUBSCRIPTION_PAYMENT_TOKEN, message: 'Invalid payment token for subscription.', }, SUBSCRIPTION_ALREADY_CANCELLED: { - errno: 180, + errno: ERRNO.SUBSCRIPTION_ALREADY_CANCELLED, message: 'Subscription has already been cancelled', }, REJECTED_CUSTOMER_UPDATE: { - errno: 181, + errno: ERRNO.REJECTED_CUSTOMER_UPDATE, message: 'Update was rejected, please try again', }, INVALID_EXPIRED_OTP_CODE: { - errno: 183, + errno: ERRNO.INVALID_EXPIRED_OTP_CODE, message: 'Invalid or expired confirmation code', version: 2, }, SERVER_BUSY: { - errno: 201, + errno: ERRNO.SERVER_BUSY, message: 'Server busy, try again soon', }, FEATURE_NOT_ENABLED: { - errno: 202, + errno: ERRNO.FEATURE_NOT_ENABLED, message: 'Feature not enabled', }, BACKEND_SERVICE_FAILURE: { - errno: 203, + errno: ERRNO.BACKEND_SERVICE_FAILURE, message: 'System unavailable, try again soon', }, DISABLED_CLIENT_ID: { - errno: 204, + errno: ERRNO.DISABLED_CLIENT_ID, message: 'System unavailable, try again soon', }, CANNOT_CREATE_PASSWORD: { - errno: 206, + errno: ERRNO.CANNOT_CREATE_PASSWORD, message: 'Can not create password, password already set', }, RECOVERY_PHONE_NUMBER_ALREADY_EXISTS: { - errno: 214, + errno: ERRNO.RECOVERY_PHONE_NUMBER_ALREADY_EXISTS, message: 'Recovery phone number already exists', }, RECOVERY_PHONE_NUMBER_DOES_NOT_EXIST: { - errno: 215, + errno: ERRNO.RECOVERY_PHONE_NUMBER_DOES_NOT_EXIST, message: 'Recovery phone number does not exist', }, SMS_SEND_RATE_LIMIT_EXCEEDED: { - errno: 216, + errno: ERRNO.SMS_SEND_RATE_LIMIT_EXCEEDED, message: 'Text message limit reached', }, RECOVERY_PHONE_REMOVE_MISSING_RECOVERY_CODES: { - errno: 218, + errno: ERRNO.RECOVERY_PHONE_REMOVE_MISSING_RECOVERY_CODES, message: 'Unable to remove recovery phone, missing backup authentication codes.', }, RECOVERY_PHONE_REGISTRATION_LIMIT_REACHED: { - errno: 219, + errno: ERRNO.RECOVERY_PHONE_REGISTRATION_LIMIT_REACHED, message: 'This phone number has been registered with too many accounts. Please try a different number.', }, TOTP_SECRET_DOES_NOT_EXIST: { - errno: 220, + errno: ERRNO.TOTP_SECRET_DOES_NOT_EXIST, message: 'TOTP secret does not exist', }, INSUFFICIENT_AAL: { - errno: 222, + errno: ERRNO.INSUFFICIENT_AAL, message: 'Insufficient AAL', }, INVALID_MFA_TOKEN: { - errno: 223, + errno: ERRNO.INVALID_MFA_TOKEN, message: 'Invalid or expired MFA token', }, SERVICE_UNAVAILABLE: { - errno: 998, + errno: ERRNO.INTERNAL_VALIDATION_ERROR, message: 'System unavailable, try again soon', }, UNEXPECTED_ERROR: { - errno: 999, + errno: ERRNO.UNEXPECTED_ERROR, message: UNEXPECTED_ERROR_MESSAGE, }, USER_CANCELED_LOGIN: { diff --git a/packages/fxa-settings/src/lib/gql-key-stretch-upgrade.ts b/packages/fxa-settings/src/lib/gql-key-stretch-upgrade.ts index a35bd598ca..20c6385a96 100644 --- a/packages/fxa-settings/src/lib/gql-key-stretch-upgrade.ts +++ b/packages/fxa-settings/src/lib/gql-key-stretch-upgrade.ts @@ -21,6 +21,7 @@ import { createSaltV2 } from 'fxa-auth-client/lib/salt'; import { deriveHawkCredentials } from 'fxa-auth-client/lib/hawk'; import { getHandledError } from './error-utils'; import { SensitiveDataClient } from './sensitive-data-client'; +import { ERRNO } from '@fxa/accounts/errors'; export type V1Credentials = { authPW: string; @@ -187,10 +188,10 @@ export class GqlKeyStretchUpgrade { } catch (error) { const errno = getHandledError(error).error.errno; - if (errno === 104) { + if (errno === ERRNO.ACCOUNT_UNVERIFIED) { // Session not verified. Trying again later. console.info('Account not verified. Try upgrade later.'); - } else if (errno === 138) { + } else if (errno === ERRNO.SESSION_UNVERIFIED) { // Account not verified. Trying again later. console.info('Account not verified. Try upgrade later.'); } else { diff --git a/packages/fxa-settings/src/lib/oauth/oauth-errors.ts b/packages/fxa-settings/src/lib/oauth/oauth-errors.ts index fb73d536e9..da9d9adf5c 100644 --- a/packages/fxa-settings/src/lib/oauth/oauth-errors.ts +++ b/packages/fxa-settings/src/lib/oauth/oauth-errors.ts @@ -3,6 +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'; export type AuthError = { errno: number; @@ -16,93 +17,93 @@ export const UNEXPECTED_ERROR = 'Unexpected error'; export const OAUTH_ERRORS: Record = { UNKNOWN_CLIENT: { - errno: 101, + errno: OAUTH_ERRNO.UNKNOWN_CLIENT, message: 'Unknown client', }, INCORRECT_REDIRECT: { - errno: 103, + errno: OAUTH_ERRNO.INCORRECT_REDIRECT, message: 'Incorrect redirect_uri', }, INVALID_ASSERTION: { - errno: 104, + errno: OAUTH_ERRNO.INVALID_ASSERTION, message: 'Invalid assertion', }, UNKNOWN_CODE: { - errno: 105, + errno: OAUTH_ERRNO.UNKNOWN_CODE, message: 'Unknown code', }, INCORRECT_CODE: { - errno: 106, + errno: OAUTH_ERRNO.INCORRECT_CODE, message: 'Incorrect code', }, EXPIRED_CODE: { - errno: 107, + errno: OAUTH_ERRNO.EXPIRED_CODE, message: 'Expired code', }, INVALID_TOKEN: { - errno: 108, + errno: OAUTH_ERRNO.INVALID_TOKEN, message: 'Invalid token', }, INVALID_PARAMETER: { - errno: 109, + errno: OAUTH_ERRNO.INVALID_PARAMETER, message: 'Invalid OAuth parameter: %(param)s', interpolate: true, }, INVALID_RESPONSE_TYPE: { - errno: 110, + errno: OAUTH_ERRNO.INVALID_RESPONSE_TYPE, message: UNEXPECTED_ERROR, }, UNAUTHORIZED: { - errno: 111, + errno: OAUTH_ERRNO.UNAUTHORIZED, message: 'Unauthorized', }, FORBIDDEN: { - errno: 112, + errno: OAUTH_ERRNO.FORBIDDEN, message: 'Forbidden', }, INVALID_CONTENT_TYPE: { - errno: 113, + errno: OAUTH_ERRNO.INVALID_CONTENT_TYPE, message: UNEXPECTED_ERROR, }, INVALID_SCOPES: { - errno: 114, + errno: OAUTH_ERRNO.INVALID_SCOPES, message: 'Invalid OAuth parameter: %(param)s', interpolate: true, }, EXPIRED_TOKEN: { - errno: 115, + errno: OAUTH_ERRNO.EXPIRED_TOKEN, message: 'Expired token', }, NOT_PUBLIC_CLIENT: { - errno: 116, + errno: OAUTH_ERRNO.NOT_PUBLIC_CLIENT, message: 'Not a public client', }, INCORRECT_CODE_CHALLENGE: { - errno: 117, + errno: OAUTH_ERRNO.INCORRECT_CODE_CHALLENGE, message: 'Incorrect code_challenge', }, MISSING_PKCE_PARAMETERS: { - errno: 118, + errno: OAUTH_ERRNO.MISSING_PKCE_PARAMETERS, message: 'PKCE parameters missing', }, STALE_AUTHENTICATION_TIMESTAMP: { - errno: 119, + errno: OAUTH_ERRNO.STALE_AUTH_AT, message: 'Stale authentication timestamp', }, MISMATCH_ACR_VALUES: { - errno: 120, + errno: OAUTH_ERRNO.MISMATCH_ACR_VALUES, message: 'Mismatch acr values', }, INVALID_GRANT_TYPE: { - errno: 121, + errno: OAUTH_ERRNO.INVALID_GRANT_TYPE, message: 'Invalid grant_type', }, SERVER_UNAVAILABLE: { - errno: 201, + errno: OAUTH_ERRNO.SERVER_UNAVAILABLE, message: 'System unavailable, try again soon', }, DISABLED_CLIENT_ID: { - errno: 202, + errno: OAUTH_ERRNO.DISABLED_CLIENT_ID, message: 'System unavailable, try again soon', }, SERVICE_UNAVAILABLE: { @@ -194,8 +195,8 @@ export class OAuthError extends Error { typeof error === 'string' ? OAUTH_ERRORS[error] : typeof error === 'number' - ? Object.values(OAUTH_ERRORS).find((x) => x.errno === error) - : null; + ? Object.values(OAUTH_ERRORS).find((x) => x.errno === error) + : null; let msg = err != null ? err.message : UNEXPECTED_ERROR; if (err?.interpolate) { msg = interpolate(msg, params || {}); diff --git a/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx b/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx index 630fc44377..425c546832 100644 --- a/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx +++ b/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx @@ -7,6 +7,7 @@ import { RouteComponentProps, useLocation, useNavigate } from '@reach/router'; import { useNavigateWithQuery } from '../../../lib/hooks/useNavigateWithQuery'; import { REACT_ENTRYPOINT } from '../../../constants'; import { AuthUiErrors } from '../../../lib/auth-errors/auth-errors'; +import { ERRNO } from '@fxa/accounts/errors'; import { logViewEvent, usePageViewEvent } from '../../../lib/metrics'; import { FtlMsg, hardNavigate } from 'fxa-react/lib/utils'; import { @@ -286,7 +287,7 @@ const ConfirmSignupCode = ({ // Intercept invalid parameter error and set the error message to INVALID_EXPIRED_OTP_CODE // This error occurs when the submitted code does not pass validation for the code param // e.g., if the submitted code contains spaces or characters other than numbers - if (error.errno === 107) { + if (error.errno === ERRNO.INVALID_PARAMETER) { localizedErrorMessage = ftlMsgResolver.getMsg( getErrorFtlId(AuthUiErrors.INVALID_EXPIRED_OTP_CODE), AuthUiErrors.INVALID_EXPIRED_OTP_CODE.message @@ -300,7 +301,7 @@ const ConfirmSignupCode = ({ error.errno === AuthUiErrors.INVALID_EXPIRED_OTP_CODE.errno || error.errno === AuthUiErrors.OTP_CODE_REQUIRED.errno || error.errno === AuthUiErrors.INVALID_OTP_CODE.errno || - error.errno === 107 + error.errno === ERRNO.INVALID_PARAMETER ) { setCodeErrorMessage(localizedErrorMessage); } else {