diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 0380589057..aaae271332 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -81,6 +81,59 @@ describe('Parse.User testing', () => { } }); + it('logs username taken with configured log level', async () => { + await reconfigureServer({ logLevels: { signupUsernameTaken: 'warn' } }); + const logger = require('../lib/logger').default; + loggerErrorSpy = spyOn(logger, 'error').and.callThrough(); + const loggerWarnSpy = spyOn(logger, 'warn').and.callThrough(); + + const user = new Parse.User(); + user.setUsername('dupUser'); + user.setPassword('pass'); + await user.signUp(); + + const user2 = new Parse.User(); + user2.setUsername('dupUser'); + user2.setPassword('pass2'); + + expect(loggerWarnSpy).not.toHaveBeenCalled(); + + try { + await user2.signUp(); + fail('should have thrown'); + } catch (e) { + expect(e.code).toBe(Parse.Error.USERNAME_TAKEN); + } + + expect(loggerWarnSpy).toHaveBeenCalledTimes(1); + expect(loggerErrorSpy.calls.count()).toBe(0); + }); + + it('can silence username taken log event', async () => { + await reconfigureServer({ logLevels: { signupUsernameTaken: 'silent' } }); + const logger = require('../lib/logger').default; + loggerErrorSpy = spyOn(logger, 'error').and.callThrough(); + const loggerWarnSpy = spyOn(logger, 'warn').and.callThrough(); + + const user = new Parse.User(); + user.setUsername('dupUser'); + user.setPassword('pass'); + await user.signUp(); + + const user2 = new Parse.User(); + user2.setUsername('dupUser'); + user2.setPassword('pass2'); + try { + await user2.signUp(); + fail('should have thrown'); + } catch (e) { + expect(e.code).toBe(Parse.Error.USERNAME_TAKEN); + } + + expect(loggerWarnSpy).not.toHaveBeenCalled(); + expect(loggerErrorSpy.calls.count()).toBe(0); + }); + it('user login with context', async () => { let hit = 0; const context = { foo: 'bar' }; diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 66c1d8bcea..687901a70d 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -1488,6 +1488,12 @@ module.exports.LogLevels = { 'Log level used by the Cloud Code Functions on success. Default is `info`. See [LogLevel](LogLevel.html) for available values.', default: 'info', }, + signupUsernameTaken: { + env: 'PARSE_SERVER_LOG_LEVELS_SIGNUP_USERNAME_TAKEN', + help: + 'Log level used when a sign-up fails because the username already exists. Default is `info`. See [LogLevel](LogLevel.html) for available values.', + default: 'info', + }, triggerAfter: { env: 'PARSE_SERVER_LOG_LEVELS_TRIGGER_AFTER', help: diff --git a/src/Options/docs.js b/src/Options/docs.js index 9569239ef7..4de7054187 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -325,6 +325,7 @@ * @interface LogLevels * @property {String} cloudFunctionError Log level used by the Cloud Code Functions on error. Default is `error`. See [LogLevel](LogLevel.html) for available values. * @property {String} cloudFunctionSuccess Log level used by the Cloud Code Functions on success. Default is `info`. See [LogLevel](LogLevel.html) for available values. + * @property {String} signupUsernameTaken Log level used when a sign-up fails because the username already exists. Default is `info`. See [LogLevel](LogLevel.html) for available values. * @property {String} triggerAfter Log level used by the Cloud Code Triggers `afterSave`, `afterDelete`, `afterFind`, `afterLogout`. Default is `info`. See [LogLevel](LogLevel.html) for available values. * @property {String} triggerBeforeError Log level used by the Cloud Code Triggers `beforeSave`, `beforeDelete`, `beforeFind`, `beforeLogin` on error. Default is `error`. See [LogLevel](LogLevel.html) for available values. * @property {String} triggerBeforeSuccess Log level used by the Cloud Code Triggers `beforeSave`, `beforeDelete`, `beforeFind`, `beforeLogin` on success. Default is `info`. See [LogLevel](LogLevel.html) for available values. diff --git a/src/Options/index.js b/src/Options/index.js index cdeb7cd846..0175abff9e 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -789,4 +789,8 @@ export interface LogLevels { :DEFAULT: error */ cloudFunctionError: ?string; + /* Log level used when a sign-up fails because the username already exists. Default is `info`. See [LogLevel](LogLevel.html) for available values. + :DEFAULT: info + */ + signupUsernameTaken: ?string; } diff --git a/src/middlewares.js b/src/middlewares.js index 2fedce8f08..1f4d8a0013 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -466,6 +466,8 @@ export function handleParseErrors(err, req, res, next) { if (req.config && req.config.enableExpressErrorHandler) { return next(err); } + const signupUsernameTakenLevel = + req.config?.logLevels?.signupUsernameTaken || 'info'; let httpStatus; // TODO: fill out this mapping switch (err.code) { @@ -480,7 +482,17 @@ export function handleParseErrors(err, req, res, next) { } res.status(httpStatus); res.json({ code: err.code, error: err.message }); - log.error('Parse error: ', err); + if (err.code === Parse.Error.USERNAME_TAKEN) { + if (signupUsernameTakenLevel !== 'silent') { + const loggerMethod = + typeof log[signupUsernameTakenLevel] === 'function' + ? log[signupUsernameTakenLevel].bind(log) + : log.error.bind(log); + loggerMethod('Parse error: ', err); + } + } else { + log.error('Parse error: ', err); + } } else if (err.status && err.message) { res.status(err.status); res.json({ error: err.message }); diff --git a/types/Options/index.d.ts b/types/Options/index.d.ts index ad11050648..409dc97248 100644 --- a/types/Options/index.d.ts +++ b/types/Options/index.d.ts @@ -297,5 +297,6 @@ export interface LogLevels { triggerBeforeError?: string; cloudFunctionSuccess?: string; cloudFunctionError?: string; + signupUsernameTaken?: string; } export {};