diff --git a/packages/api/src/error-handler.js b/packages/api/src/error-handler.js index d7ee45302f..40e01722ba 100644 --- a/packages/api/src/error-handler.js +++ b/packages/api/src/error-handler.js @@ -28,15 +28,15 @@ export function errorHandler (err, { log }, request) { return new JSONResponse(error, { status }) } + if (err instanceof HTTPError) { + return new JSONResponse(err, { status }) + } + let error = { code: err.code, message: err.message } - if (err instanceof HTTPError) { - return new JSONResponse(error, { status }) - } - switch (err.code) { // Magic SDK errors case MagicErrors.TokenExpired: diff --git a/packages/api/src/errors.js b/packages/api/src/errors.js index 92308da69f..234418d88a 100644 --- a/packages/api/src/errors.js +++ b/packages/api/src/errors.js @@ -117,6 +117,30 @@ export class MagicTokenRequiredError extends HTTPError { } MagicTokenRequiredError.CODE = 'ERROR_MAGIC_TOKEN_REQUIRED' +/** + * Error indicating a new user signup was denied and probably will be indefinitely, + * and the user should try a new product instead. + */ +export class NewUserDeniedTryOtherProductError extends HTTPError { + /** + * @param {string} message + * @param {URL} otherProduct + */ + constructor (message, otherProduct) { + super(message, 403) + this.code = 'NEW_USER_DENIED_TRY_OTHER_PRODUCT' + this.otherProduct = otherProduct + } + + toJSON () { + return { + message: this.message, + code: this.code, + otherProduct: this.otherProduct.toString() + } + } +} + export class AgreementsRequiredError extends HTTPError { /** * @param {import("./utils/billing-types").Agreement[]} agreements diff --git a/packages/api/src/user.js b/packages/api/src/user.js index 0fac1e56c4..178d8478fc 100644 --- a/packages/api/src/user.js +++ b/packages/api/src/user.js @@ -1,7 +1,7 @@ import * as JWT from './utils/jwt.js' import { JSONResponse, notFound } from './utils/json-response.js' import { JWT_ISSUER } from './constants.js' -import { HTTPError, PSAErrorInvalidData, PSAErrorRequiredData, PSAErrorResourceNotFound, RangeNotSatisfiableError } from './errors.js' +import { HTTPError, PSAErrorInvalidData, PSAErrorRequiredData, PSAErrorResourceNotFound, RangeNotSatisfiableError, NewUserDeniedTryOtherProductError } from './errors.js' import { getTagValue, hasPendingTagProposal, hasTag } from './utils/tags.js' import { NO_READ_OR_WRITE, @@ -140,7 +140,8 @@ async function loginOrRegister (request, env) { if (newUserRegistrationIsClosed) { const user = await env.db.getUser(parsed.issuer, {}) if (!user) { - throw new HTTPError('new user registration is closed. Try creating an account at https://console.web3.storage', 403) + const otherProduct = new URL('https://console.web3.storage/') + throw new NewUserDeniedTryOtherProductError(`new user registration is closed. Try creating an account at ${otherProduct.toString()}`, otherProduct) } } diff --git a/packages/api/test/user.spec.js b/packages/api/test/user.spec.js index 5596d32c29..c4c4302d16 100644 --- a/packages/api/test/user.spec.js +++ b/packages/api/test/user.spec.js @@ -587,6 +587,7 @@ describe('userLoginPost', function () { const authToken = createMagicTestModeToken(user.publicAddress, user.claims) const userPostLoginResponse = await fetch(loginEndpoint, { headers: { + accept: 'application/json', authorization: `Bearer ${authToken}`, contentType: 'application/json' }, @@ -599,6 +600,12 @@ describe('userLoginPost', function () { const responseText = await userPostLoginResponse.text() assert.equal(userPostLoginResponse.status, 403, 'response status code is 403 forbidden because new user registration is forbidden') assert.ok(responseText.includes('new user registration is closed'), 'response body indicates new user registration is closed') + + const responseJson = JSON.parse(responseText) + assert.equal(typeof responseJson, 'object', 'response can be parsed as json') + assert.ok(responseJson.message.includes('new user registration is closed'), 'response object message indicates new user registration is closed') + assert.equal(responseJson.code, 'NEW_USER_DENIED_TRY_OTHER_PRODUCT') + assert.equal(responseJson.otherProduct, 'https://console.web3.storage/') }) }) })