Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions resources/buildConfigDefinitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const nestedOptionTypes = [
'SecurityOptions',
'SchemaOptions',
'LogLevels',
'LogEvents',
];

/** The prefix of environment variables for nested options. */
Expand All @@ -39,6 +40,7 @@ const nestedOptionEnvPrefix = {
LogClientEvent: 'PARSE_SERVER_DATABASE_LOG_CLIENT_EVENTS_',
LogLevel: 'PARSE_SERVER_LOG_LEVEL_',
LogLevels: 'PARSE_SERVER_LOG_LEVELS_',
LogEvents: 'PARSE_SERVER_LOG_EVENTS_',
PagesCustomUrlsOptions: 'PARSE_SERVER_PAGES_CUSTOM_URL_',
PagesOptions: 'PARSE_SERVER_PAGES_',
PagesRoute: 'PARSE_SERVER_PAGES_ROUTE_',
Expand Down
53 changes: 53 additions & 0 deletions spec/ParseUser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,59 @@ describe('Parse.User testing', () => {
}
});

it('logs username taken with configured log level', async () => {
await reconfigureServer({ logEvents: { usernameAlreadyExists: '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({ logEvents: { usernameAlreadyExists: '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' };
Expand Down
16 changes: 16 additions & 0 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
FileUploadOptions,
IdempotencyOptions,
LogLevels,
LogEvents,
PagesOptions,
ParseServerOptions,
SchemaOptions,
Expand Down Expand Up @@ -128,6 +129,7 @@ export class Config {
requestKeywordDenylist,
allowExpiredAuthDataToken,
logLevels,
logEvents,
rateLimit,
databaseOptions,
extendSessionOnUse,
Expand Down Expand Up @@ -170,6 +172,7 @@ export class Config {
this.validateRequestKeywordDenylist(requestKeywordDenylist);
this.validateRateLimit(rateLimit);
this.validateLogLevels(logLevels);
this.validateLogEvents(logEvents);
this.validateDatabaseOptions(databaseOptions);
this.validateCustomPages(customPages);
this.validateAllowClientClassCreation(allowClientClassCreation);
Expand Down Expand Up @@ -641,6 +644,19 @@ export class Config {
}
}

static validateLogEvents(logEvents) {
for (const key of Object.keys(LogEvents)) {
if (logEvents[key]) {
// We validate that each configured event uses a valid log *level* (same list as logLevels).
if (validLogLevels.indexOf(logEvents[key]) === -1) {
throw `'${key}' must be one of ${JSON.stringify(validLogLevels)}`;
}
} else {
logEvents[key] = LogEvents[key].default;
}
}
}

static validateDatabaseOptions(databaseOptions) {
if (databaseOptions == undefined) {
return;
Expand Down
15 changes: 15 additions & 0 deletions src/Options/Definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,13 @@ module.exports.ParseServerOptions = {
action: parsers.objectParser,
type: 'LiveQueryServerOptions',
},
logEvents: {
env: 'PARSE_SERVER_LOG_EVENTS',
help: '(Optional) Overrides the log levels used by specific log events.',
action: parsers.objectParser,
type: 'LogEvents',
default: {},
},
loggerAdapter: {
env: 'PARSE_SERVER_LOGGER_ADAPTER',
help: 'Adapter module for the logging sub-system',
Expand Down Expand Up @@ -1507,3 +1514,11 @@ module.exports.LogLevels = {
default: 'info',
},
};
module.exports.LogEvents = {
usernameAlreadyExists: {
env: 'PARSE_SERVER_LOG_EVENTS_USERNAME_ALREADY_EXISTS',
help:
'Log level used when a sign-up fails because the username already exists. Default is `error`. See [LogLevel](LogLevel.html) for available values.',
default: 'error',
},
};
6 changes: 6 additions & 0 deletions src/Options/docs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ export interface ParseServerOptions {
/* (Optional) Overrides the log levels used internally by Parse Server to log events.
:DEFAULT: {} */
logLevels: ?LogLevels;
/* (Optional) Overrides the log levels used by specific log events.
:DEFAULT: {} */
logEvents: ?LogEvents;
/* 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) */
maxLogFiles: ?NumberOrString;
/* Disables console output
Expand Down Expand Up @@ -790,3 +793,10 @@ export interface LogLevels {
*/
cloudFunctionError: ?string;
}

export interface LogEvents {
/* Log level used when a sign-up fails because the username already exists. Default is `error`. See [LogLevel](LogLevel.html) for available values.
:DEFAULT: error
*/
usernameAlreadyExists: ?string;
}
13 changes: 12 additions & 1 deletion src/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ export function handleParseErrors(err, req, res, next) {
if (req.config && req.config.enableExpressErrorHandler) {
return next(err);
}
const usernameAlreadyExistsLevel = req.config?.logEvents?.usernameAlreadyExists || 'error';
let httpStatus;
// TODO: fill out this mapping
switch (err.code) {
Expand All @@ -480,7 +481,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 (usernameAlreadyExistsLevel !== 'silent') {
const loggerMethod =
typeof log[usernameAlreadyExistsLevel] === 'function'
? log[usernameAlreadyExistsLevel].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 });
Expand Down
4 changes: 4 additions & 0 deletions types/Options/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface ParseServerOptions {
verbose?: boolean;
logLevel?: string;
logLevels?: LogLevels;
logEvents?: LogEvents;
maxLogFiles?: NumberOrString;
silent?: boolean;
databaseURI: string;
Expand Down Expand Up @@ -298,4 +299,7 @@ export interface LogLevels {
cloudFunctionSuccess?: string;
cloudFunctionError?: string;
}
export interface LogEvents {
usernameAlreadyExists?: string;
}
export {};
Loading