Skip to content

Commit f75fad7

Browse files
committed
feat: Add option to change log levels of username already exists message
1 parent e78e58d commit f75fad7

File tree

8 files changed

+118
-1
lines changed

8 files changed

+118
-1
lines changed

resources/buildConfigDefinitions.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const nestedOptionTypes = [
2525
'SecurityOptions',
2626
'SchemaOptions',
2727
'LogLevels',
28+
'LogEvents',
2829
];
2930

3031
/** The prefix of environment variables for nested options. */
@@ -39,6 +40,7 @@ const nestedOptionEnvPrefix = {
3940
LogClientEvent: 'PARSE_SERVER_DATABASE_LOG_CLIENT_EVENTS_',
4041
LogLevel: 'PARSE_SERVER_LOG_LEVEL_',
4142
LogLevels: 'PARSE_SERVER_LOG_LEVELS_',
43+
LogEvents: 'PARSE_SERVER_LOG_EVENTS_',
4244
PagesCustomUrlsOptions: 'PARSE_SERVER_PAGES_CUSTOM_URL_',
4345
PagesOptions: 'PARSE_SERVER_PAGES_',
4446
PagesRoute: 'PARSE_SERVER_PAGES_ROUTE_',

spec/ParseUser.spec.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,59 @@ describe('Parse.User testing', () => {
8181
}
8282
});
8383

84+
it('logs username taken with configured log level', async () => {
85+
await reconfigureServer({ logEvents: { usernameAlreadyExists: 'warn' } });
86+
const logger = require('../lib/logger').default;
87+
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
88+
const loggerWarnSpy = spyOn(logger, 'warn').and.callThrough();
89+
90+
const user = new Parse.User();
91+
user.setUsername('dupUser');
92+
user.setPassword('pass');
93+
await user.signUp();
94+
95+
const user2 = new Parse.User();
96+
user2.setUsername('dupUser');
97+
user2.setPassword('pass2');
98+
99+
expect(loggerWarnSpy).not.toHaveBeenCalled();
100+
101+
try {
102+
await user2.signUp();
103+
fail('should have thrown');
104+
} catch (e) {
105+
expect(e.code).toBe(Parse.Error.USERNAME_TAKEN);
106+
}
107+
108+
expect(loggerWarnSpy).toHaveBeenCalledTimes(1);
109+
expect(loggerErrorSpy.calls.count()).toBe(0);
110+
});
111+
112+
it('can silence username taken log event', async () => {
113+
await reconfigureServer({ logEvents: { usernameAlreadyExists: 'silent' } });
114+
const logger = require('../lib/logger').default;
115+
loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
116+
const loggerWarnSpy = spyOn(logger, 'warn').and.callThrough();
117+
118+
const user = new Parse.User();
119+
user.setUsername('dupUser');
120+
user.setPassword('pass');
121+
await user.signUp();
122+
123+
const user2 = new Parse.User();
124+
user2.setUsername('dupUser');
125+
user2.setPassword('pass2');
126+
try {
127+
await user2.signUp();
128+
fail('should have thrown');
129+
} catch (e) {
130+
expect(e.code).toBe(Parse.Error.USERNAME_TAKEN);
131+
}
132+
133+
expect(loggerWarnSpy).not.toHaveBeenCalled();
134+
expect(loggerErrorSpy.calls.count()).toBe(0);
135+
});
136+
84137
it('user login with context', async () => {
85138
let hit = 0;
86139
const context = { foo: 'bar' };

src/Config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
FileUploadOptions,
1515
IdempotencyOptions,
1616
LogLevels,
17+
LogEvents,
1718
PagesOptions,
1819
ParseServerOptions,
1920
SchemaOptions,
@@ -128,6 +129,7 @@ export class Config {
128129
requestKeywordDenylist,
129130
allowExpiredAuthDataToken,
130131
logLevels,
132+
logEvents,
131133
rateLimit,
132134
databaseOptions,
133135
extendSessionOnUse,
@@ -170,6 +172,7 @@ export class Config {
170172
this.validateRequestKeywordDenylist(requestKeywordDenylist);
171173
this.validateRateLimit(rateLimit);
172174
this.validateLogLevels(logLevels);
175+
this.validateLogEvents(logEvents);
173176
this.validateDatabaseOptions(databaseOptions);
174177
this.validateCustomPages(customPages);
175178
this.validateAllowClientClassCreation(allowClientClassCreation);
@@ -641,6 +644,19 @@ export class Config {
641644
}
642645
}
643646

647+
static validateLogEvents(logEvents) {
648+
for (const key of Object.keys(LogEvents)) {
649+
if (logEvents[key]) {
650+
// We validate that each configured event uses a valid log *level* (same list as logLevels).
651+
if (validLogLevels.indexOf(logEvents[key]) === -1) {
652+
throw `'${key}' must be one of ${JSON.stringify(validLogLevels)}`;
653+
}
654+
} else {
655+
logEvents[key] = LogEvents[key].default;
656+
}
657+
}
658+
}
659+
644660
static validateDatabaseOptions(databaseOptions) {
645661
if (databaseOptions == undefined) {
646662
return;

src/Options/Definitions.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,13 @@ module.exports.ParseServerOptions = {
352352
action: parsers.objectParser,
353353
type: 'LiveQueryServerOptions',
354354
},
355+
logEvents: {
356+
env: 'PARSE_SERVER_LOG_EVENTS',
357+
help: '(Optional) Overrides the log levels used by specific log events.',
358+
action: parsers.objectParser,
359+
type: 'LogEvents',
360+
default: {},
361+
},
355362
loggerAdapter: {
356363
env: 'PARSE_SERVER_LOGGER_ADAPTER',
357364
help: 'Adapter module for the logging sub-system',
@@ -1507,3 +1514,11 @@ module.exports.LogLevels = {
15071514
default: 'info',
15081515
},
15091516
};
1517+
module.exports.LogEvents = {
1518+
usernameAlreadyExists: {
1519+
env: 'PARSE_SERVER_LOG_EVENTS_USERNAME_ALREADY_EXISTS',
1520+
help:
1521+
'Log level used when a sign-up fails because the username already exists. Default is `error`. See [LogLevel](LogLevel.html) for available values.',
1522+
default: 'error',
1523+
},
1524+
};

src/Options/docs.js

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Options/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ export interface ParseServerOptions {
9898
/* (Optional) Overrides the log levels used internally by Parse Server to log events.
9999
:DEFAULT: {} */
100100
logLevels: ?LogLevels;
101+
/* (Optional) Overrides the log levels used by specific log events.
102+
:DEFAULT: {} */
103+
logEvents: ?LogEvents;
101104
/* Maximum number of logs to keep. If not set, no logs will be removed. This can be a number of files or number of days. If using days, add 'd' as the suffix. (default: null) */
102105
maxLogFiles: ?NumberOrString;
103106
/* Disables console output
@@ -790,3 +793,10 @@ export interface LogLevels {
790793
*/
791794
cloudFunctionError: ?string;
792795
}
796+
797+
export interface LogEvents {
798+
/* Log level used when a sign-up fails because the username already exists. Default is `error`. See [LogLevel](LogLevel.html) for available values.
799+
:DEFAULT: error
800+
*/
801+
usernameAlreadyExists: ?string;
802+
}

src/middlewares.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ export function handleParseErrors(err, req, res, next) {
466466
if (req.config && req.config.enableExpressErrorHandler) {
467467
return next(err);
468468
}
469+
const usernameAlreadyExistsLevel = req.config?.logEvents?.usernameAlreadyExists || 'error';
469470
let httpStatus;
470471
// TODO: fill out this mapping
471472
switch (err.code) {
@@ -480,7 +481,17 @@ export function handleParseErrors(err, req, res, next) {
480481
}
481482
res.status(httpStatus);
482483
res.json({ code: err.code, error: err.message });
483-
log.error('Parse error: ', err);
484+
if (err.code === Parse.Error.USERNAME_TAKEN) {
485+
if (usernameAlreadyExistsLevel !== 'silent') {
486+
const loggerMethod =
487+
typeof log[usernameAlreadyExistsLevel] === 'function'
488+
? log[usernameAlreadyExistsLevel].bind(log)
489+
: log.error.bind(log);
490+
loggerMethod('Parse error: ', err);
491+
}
492+
} else {
493+
log.error('Parse error: ', err);
494+
}
484495
} else if (err.status && err.message) {
485496
res.status(err.status);
486497
res.json({ error: err.message });

types/Options/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface ParseServerOptions {
4747
verbose?: boolean;
4848
logLevel?: string;
4949
logLevels?: LogLevels;
50+
logEvents?: LogEvents;
5051
maxLogFiles?: NumberOrString;
5152
silent?: boolean;
5253
databaseURI: string;
@@ -298,4 +299,7 @@ export interface LogLevels {
298299
cloudFunctionSuccess?: string;
299300
cloudFunctionError?: string;
300301
}
302+
export interface LogEvents {
303+
usernameAlreadyExists?: string;
304+
}
301305
export {};

0 commit comments

Comments
 (0)