add InvalidCredentialsError with language-agnostic code (#15472)

* add InvalidCredentialsError with language-agnostic code and update backend & web logout logic

* error.code in API error responses

Updated the error handler to serialize responseErr.code as the JSON error field, falling back to responseErr.name when no code is set.

* fix(lint): whitespace and missing def

* fix(lint): missed one

* add InvalidCredentialsError case for bad token

Add test verifying that auth middleware throws InvalidCredentialsError with code "invalid_credentials" and correct translated message when the API token is invalid.

* fix(test): user fields implicitly required

---------

Co-authored-by: Kalista Payne <sabrecat@gmail.com>
This commit is contained in:
Fiz
2025-07-15 09:49:11 -05:00
committed by GitHub
parent 03c7e9172e
commit f26d2a59ae
5 changed files with 45 additions and 6 deletions

View File

@@ -60,5 +60,20 @@ describe('auth middleware', () => {
return done();
});
});
it('errors with InvalidCredentialsError and code when token is wrong', done => {
const authWithHeaders = authWithHeadersFactory({ userFieldsToExclude: [] });
req.headers['x-api-user'] = user._id;
req.headers['x-api-key'] = 'totally-wrong-token';
authWithHeaders(req, res, err => {
expect(err).to.exist;
expect(err.name).to.equal('InvalidCredentialsError');
expect(err.code).to.equal('invalid_credentials');
expect(err.message).to.equal(res.t('invalidCredentials'));
return done();
});
});
});
});

View File

@@ -223,11 +223,10 @@ export default {
const errorData = error.response.data;
const errorMessage = errorData.message || errorData;
const errorCode = errorData.error;
// Check for conditions to reset the user auth
// TODO use a specific error like NotificationNotFound instead of checking for the string
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
if (invalidUserMessage.indexOf(errorMessage) !== -1) {
// If 'invalid_credentials' signaled, force logout
if (error.response.status === 401 && errorCode === 'invalid_credentials') {
this.$store.dispatch('auth:logout', { redirectToLogin: true });
return null;
}

View File

@@ -117,3 +117,27 @@ export class InternalServerError extends CustomError {
this.message = customMessage || 'An unexpected error occurred.';
}
}
/**
* @apiDefine InvalidCredentials
* @apiError InvalidCredentials The users credentials are no longer valid.
*
* @apiNote
* The 'invalid_credentials' error code is language-agnostic:
* clients should use this code (regardless of locale or translated message)
* to unambiguously trigger a user logout.
*
* @apiErrorExample Error-Response:
* HTTP/1.1 401 Unauthorized
* {
* "error": "invalid_credentials",
* "message": "There is no account that uses those credentials."
* }
*/
export class InvalidCredentialsError extends NotAuthorized {
constructor (message) {
super(message);
this.name = this.constructor.name;
this.code = 'invalid_credentials';
}
}

View File

@@ -2,6 +2,7 @@ import moment from 'moment';
import nconf from 'nconf';
import url from 'url';
import {
InvalidCredentialsError,
NotAuthorized,
} from '../libs/errors';
import {
@@ -81,7 +82,7 @@ export function authWithHeaders (options = {}) {
.exec()
.then(user => {
if (!user || apiToken !== user.apiToken) {
throw new NotAuthorized(res.t('invalidCredentials'));
throw new InvalidCredentialsError(res.t('invalidCredentials'));
}
if (user.auth.blocked) {

View File

@@ -82,7 +82,7 @@ export default function errorHandler (err, req, res, next) { // eslint-disable-l
const jsonRes = {
success: false,
error: responseErr.name,
error: responseErr.code || responseErr.name,
message: responseErr.message,
};