From 3e6ffd732fad17feb3f698efb6f3e6fb23e35ca3 Mon Sep 17 00:00:00 2001 From: Vijay Budhram Date: Wed, 12 Aug 2026 13:39:37 -0400 Subject: [PATCH] fix(errors): return 403 from featureNotEnabled instead of 503 --- libs/accounts/errors/src/app-error.ts | 2 +- libs/accounts/errors/src/index.spec.ts | 10 ++++++++++ .../fxa-auth-server/docs/swagger/auth-server-api.ts | 2 +- .../docs/swagger/devices-and-sessions-api.ts | 4 ++-- .../lib/routes/devices-and-sessions.spec.ts | 10 +++++----- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/libs/accounts/errors/src/app-error.ts b/libs/accounts/errors/src/app-error.ts index 5d4b02fcade..8ace7712c1d 100644 --- a/libs/accounts/errors/src/app-error.ts +++ b/libs/accounts/errors/src/app-error.ts @@ -602,7 +602,7 @@ export class AppError extends Error { } return new AppError( { - code: 503, + code: 403, error: 'Feature not enabled', errno: ERRNO.FEATURE_NOT_ENABLED, message: 'Feature not enabled', diff --git a/libs/accounts/errors/src/index.spec.ts b/libs/accounts/errors/src/index.spec.ts index 399e89e23c8..87264046b21 100644 --- a/libs/accounts/errors/src/index.spec.ts +++ b/libs/accounts/errors/src/index.spec.ts @@ -213,6 +213,16 @@ describe('AppErrors', () => { } ); + it('featureNotEnabled', () => { + const result = AppError.featureNotEnabled(); + expect(result).toBeInstanceOf(AppError); + expect(result.errno).toEqual(202); + expect(result.message).toEqual('Feature not enabled'); + expect(result.output.statusCode).toEqual(403); + expect(result.output.payload.error).toEqual('Feature not enabled'); + expect(result.output.payload.errno).toEqual(202); + }); + it('iapInvalidToken', () => { const defaultErrorMessage = 'Invalid IAP token'; let result = AppError.iapInvalidToken(); diff --git a/packages/fxa-auth-server/docs/swagger/auth-server-api.ts b/packages/fxa-auth-server/docs/swagger/auth-server-api.ts index e2bc837782a..3c34354b9bc 100644 --- a/packages/fxa-auth-server/docs/swagger/auth-server-api.ts +++ b/packages/fxa-auth-server/docs/swagger/auth-server-api.ts @@ -152,7 +152,7 @@ export const AUTH_SERVER_API_DESCRIPTION = { | 404 | 198 | Unknown app name | | 400 | 199 | Invalid promotion code | | 503 | 201 | Service unavailable | - | 503 | 202 | Feature not enabled | + | 403 | 202 | Feature not enabled | | 500 | 203 | System unavailable, try again soon | | 503 | 204 | This client has been temporarily disabled | | 500 | 205 | Could not login with third party account, please try again later | diff --git a/packages/fxa-auth-server/docs/swagger/devices-and-sessions-api.ts b/packages/fxa-auth-server/docs/swagger/devices-and-sessions-api.ts index 17d9c4fc7e6..775d526af67 100644 --- a/packages/fxa-auth-server/docs/swagger/devices-and-sessions-api.ts +++ b/packages/fxa-auth-server/docs/swagger/devices-and-sessions-api.ts @@ -89,7 +89,7 @@ const ACCOUNT_DEVICE_POST = { - \`errno: 107\` - Invalid parameter in request body `, }, - 503: { + 403: { description: dedent` Failing requests may be caused by the following errors (this is not an exhaustive list): - \`errno: 202\` - Feature not enabled @@ -159,7 +159,7 @@ const ACCOUNT_DEVICES_NOTIFY_POST = { - \`errno: 107\` - Invalid parameter in request body `, }, - 503: { + 403: { description: dedent` Failing requests may be caused by the following errors (this is not an exhaustive list): - \`errno: 202\` - Feature not enabled diff --git a/packages/fxa-auth-server/lib/routes/devices-and-sessions.spec.ts b/packages/fxa-auth-server/lib/routes/devices-and-sessions.spec.ts index da1be9e5b42..a7158cc41c0 100644 --- a/packages/fxa-auth-server/lib/routes/devices-and-sessions.spec.ts +++ b/packages/fxa-auth-server/lib/routes/devices-and-sessions.spec.ts @@ -219,7 +219,7 @@ describe('/account/device', () => { throw new Error('should have thrown'); }, (err: any) => { - expect(err.output.statusCode).toBe(503); + expect(err.output.statusCode).toBe(403); expect(err.errno).toBe(error.ERRNO.FEATURE_NOT_ENABLED); } ); @@ -504,7 +504,7 @@ describe('/account/devices/notify', () => { throw new Error('should have thrown'); }, (err: any) => { - expect(err.output.statusCode).toBe(503); + expect(err.output.statusCode).toBe(403); expect(err.errno).toBe(error.ERRNO.FEATURE_NOT_ENABLED); } ); @@ -821,7 +821,7 @@ describe('/account/device/commands', () => { mockRequest.auth.credentials.refreshTokenId = 'aaabbbccc'; await expect(route.handler(mockRequest)).rejects.toMatchObject({ - output: { statusCode: 503 }, + output: { statusCode: 403 }, errno: error.ERRNO.FEATURE_NOT_ENABLED, }); expect(mockPushbox.retrieve).not.toHaveBeenCalled(); @@ -1310,7 +1310,7 @@ describe('/account/devices/invoke_command', () => { mockRequest.auth.credentials.refreshTokenId = 'aaabbbccc'; await expect(route.handler(mockRequest)).rejects.toMatchObject({ - output: { statusCode: 503 }, + output: { statusCode: 403 }, errno: error.ERRNO.FEATURE_NOT_ENABLED, }); expect(mockPushbox.store).not.toHaveBeenCalled(); @@ -1673,7 +1673,7 @@ describe('/account/devices', () => { }); await expect(route.handler(mockRequest)).rejects.toMatchObject({ - output: { statusCode: 503 }, + output: { statusCode: 403 }, errno: error.ERRNO.FEATURE_NOT_ENABLED, }); });