Skip to content

Commit 8463e91

Browse files
committed
Merge branch 'pr-migrations'
2 parents 351d1df + d3ba3bb commit 8463e91

File tree

9 files changed

+38
-45
lines changed

9 files changed

+38
-45
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ ___
114114
- Added Deprecation Policy to govern the introduction of breaking changes in a phased pattern that is more predictable for developers (Manuel Trezza) [#7199](https://github.com/parse-community/parse-server/pull/7199)
115115
- Add REST API endpoint `/loginAs` to create session of any user with master key; allows to impersonate another user. (GormanFletcher) [#7406](https://github.com/parse-community/parse-server/pull/7406)
116116
- Add official support for MongoDB 5.0 (Manuel Trezza) [#7469](https://github.com/parse-community/parse-server/pull/7469)
117+
- Add user-defined schema and migrations (Antoine Cormouls, Samuel Denis-D'Ortun) [#7418](https://github.com/parse-community/parse-server/pull/7418)
117118

118119
### Other Changes
119120
- Support native mongodb syntax in aggregation pipelines (Raschid JF Rafeally) [#7339](https://github.com/parse-community/parse-server/pull/7339)

src/Adapters/Storage/Mongo/MongoSchemaCollection.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import MongoCollection from './MongoCollection';
22
import Parse from 'parse/node';
3+
import _ from 'lodash'
34

45
function mongoFieldToParseSchemaField(type) {
56
if (type[0] === '*') {
@@ -276,8 +277,9 @@ class MongoSchemaCollection {
276277
}
277278

278279
async updateFieldOptions(className: string, fieldName: string, fieldType: any) {
279-
// eslint-disable-next-line no-unused-vars
280-
const { type, targetClass, ...fieldOptions } = fieldType;
280+
const fieldOptions = _.cloneDeep(fieldType);
281+
delete fieldOptions.type
282+
delete fieldOptions.targetClass
281283
await this.upsertSchema(
282284
className,
283285
{ [fieldName]: { $exists: true } },

src/Config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
SchemaOptions,
1515
} from './Options/Definitions';
1616
import { isBoolean, isString, isArray } from 'lodash';
17-
import type { MigrationsOptions } from './SchemaMigrations/Migrations';
1817

1918
function removeTrailingSlash(str) {
2019
if (!str) {
@@ -77,6 +76,7 @@ export class Config {
7776
fileUpload,
7877
pages,
7978
security,
79+
schema,
8080
}) {
8181
if (masterKey === readOnlyMasterKey) {
8282
throw new Error('masterKey and readOnlyMasterKey should be different');
@@ -113,7 +113,7 @@ export class Config {
113113
this.validateIdempotencyOptions(idempotencyOptions);
114114
this.validatePagesOptions(pages);
115115
this.validateSecurityOptions(security);
116-
this.validateSchemaOptions(security);
116+
this.validateSchemaOptions(schema);
117117
}
118118

119119
static validateSecurityOptions(security) {
@@ -132,7 +132,7 @@ export class Config {
132132
}
133133
}
134134

135-
static validateSchemaOptions(schema: MigrationsOptions) {
135+
static validateSchemaOptions(schema: SchemaOptions) {
136136
if (Object.prototype.toString.call(schema) !== '[object Object]') {
137137
throw 'Parse Server option schema must be an object.';
138138
}

src/Options/Definitions.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -441,13 +441,6 @@ module.exports.SecurityOptions = {
441441
},
442442
};
443443
module.exports.SchemaOptions = {
444-
// definitions: JSONSchema[];
445-
// strict: ?boolean;
446-
// deleteExtraFields: ?boolean;
447-
// recreateModifiedFields: ?boolean;
448-
// lockSchemas: ?boolean;
449-
// beforeMigration: ?() => void | Promise<void>;
450-
// afterMigration: ?() => void | Promise<void>;
451444
definitions: {
452445
help: 'The schema definitions.',
453446
default: [],

src/Options/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { MailAdapter } from '../Adapters/Email/MailAdapter';
88
import { PubSubAdapter } from '../Adapters/PubSub/PubSubAdapter';
99
import { WSSAdapter } from '../Adapters/WebSocketServer/WSSAdapter';
1010
import { CheckGroup } from '../Security/CheckGroup';
11-
import type { MigrationsOptions } from '../SchemaMigrations/Migrations';
11+
import type { SchemaOptions } from '../SchemaMigrations/Migrations';
1212

1313
type Adapter<T> = string | any | T;
1414
type NumberOrBoolean = number | boolean;
@@ -243,7 +243,7 @@ export interface ParseServerOptions {
243243
/* Callback when server has started */
244244
serverStartComplete: ?(error: ?Error) => void;
245245
/* Rest representation on Parse.Schema https://docs.parseplatform.org/rest/guide/#adding-a-schema */
246-
schema: ?MigrationsOptions;
246+
schema: ?SchemaOptions;
247247
/* Callback when server has closed */
248248
serverCloseComplete: ?() => void;
249249
/* The security options to identify and report weak security settings.

src/SchemaMigrations/DefinedSchemas.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,22 @@ import * as Migrations from './Migrations';
99

1010
export class DefinedSchemas {
1111
config: ParseServerOptions;
12-
migrationsOptions: Migrations.MigrationsOptions;
12+
schemaOptions: Migrations.SchemaOptions;
1313
localSchemas: Migrations.JSONSchema[];
1414
retries: number;
1515
maxRetries: number;
1616

17-
constructor(migrationsOptions: Migrations.MigrationsOptions, config: ParseServerOptions) {
17+
constructor(schemaOptions: Migrations.SchemaOptions, config: ParseServerOptions) {
1818
this.localSchemas = [];
1919
this.config = Config.get(config.appId);
20-
this.migrationsOptions = migrationsOptions;
20+
this.schemaOptions = schemaOptions;
2121

22-
if (migrationsOptions && migrationsOptions.definitions) {
23-
if (!Array.isArray(migrationsOptions.definitions)) {
22+
if (schemaOptions && schemaOptions.definitions) {
23+
if (!Array.isArray(schemaOptions.definitions)) {
2424
throw `"schema.definitions" must be an array of schemas`;
2525
}
2626

27-
this.localSchemas = migrationsOptions.definitions;
27+
this.localSchemas = schemaOptions.definitions;
2828
}
2929

3030
this.retries = 0;
@@ -66,14 +66,14 @@ export class DefinedSchemas {
6666
async execute() {
6767
try {
6868
logger.info('Running Migrations');
69-
if (this.migrationsOptions && this.migrationsOptions.beforeMigration) {
70-
await Promise.resolve(this.migrationsOptions.beforeMigration());
69+
if (this.schemaOptions && this.schemaOptions.beforeMigration) {
70+
await Promise.resolve(this.schemaOptions.beforeMigration());
7171
}
7272

7373
await this.executeMigrations();
7474

75-
if (this.migrationsOptions && this.migrationsOptions.afterMigration) {
76-
await Promise.resolve(this.migrationsOptions.afterMigration());
75+
if (this.schemaOptions && this.schemaOptions.afterMigration) {
76+
await Promise.resolve(this.schemaOptions.afterMigration());
7777
}
7878

7979
logger.info('Running Migrations Completed');
@@ -122,7 +122,7 @@ export class DefinedSchemas {
122122
}
123123

124124
checkForMissingSchemas() {
125-
if (this.migrationsOptions.strict !== true) {
125+
if (this.schemaOptions.strict !== true) {
126126
return;
127127
}
128128

@@ -141,7 +141,7 @@ export class DefinedSchemas {
141141
process.exit(1);
142142
}
143143

144-
if (this.migrationsOptions.strict && missingSchemas.length) {
144+
if (this.schemaOptions.strict && missingSchemas.length) {
145145
logger.warn(
146146
`The following schemas are currently present in the database, but not explicitly defined in a schema: "${missingSchemas.join(
147147
'", "'
@@ -273,22 +273,22 @@ export class DefinedSchemas {
273273
}
274274
});
275275

276-
if (this.migrationsOptions.deleteExtraFields === true) {
276+
if (this.schemaOptions.deleteExtraFields === true) {
277277
fieldsToDelete.forEach(fieldName => {
278278
newLocalSchema.deleteField(fieldName);
279279
});
280280

281281
// Delete fields from the schema then apply changes
282282
await this.updateSchemaToDB(newLocalSchema);
283-
} else if (this.migrationsOptions.strict === true && fieldsToDelete.length) {
283+
} else if (this.schemaOptions.strict === true && fieldsToDelete.length) {
284284
logger.warn(
285285
`The following fields exist in the database for "${
286286
localSchema.className
287287
}", but are missing in the schema : "${fieldsToDelete.join('" ,"')}"`
288288
);
289289
}
290290

291-
if (this.migrationsOptions.recreateModifiedFields === true) {
291+
if (this.schemaOptions.recreateModifiedFields === true) {
292292
fieldsToRecreate.forEach(field => {
293293
newLocalSchema.deleteField(field.fieldName);
294294
});
@@ -300,7 +300,7 @@ export class DefinedSchemas {
300300
const field = localSchema.fields[fieldInfo.fieldName];
301301
this.handleFields(newLocalSchema, fieldInfo.fieldName, field);
302302
});
303-
} else if (this.migrationsOptions.strict === true && fieldsToRecreate.length) {
303+
} else if (this.schemaOptions.strict === true && fieldsToRecreate.length) {
304304
fieldsToRecreate.forEach(field => {
305305
const from =
306306
field.from.type + (field.from.targetClass ? ` (${field.from.targetClass})` : '');

src/SchemaMigrations/Migrations.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface IndexesInterface {
3535
[key: string]: IndexInterface;
3636
}
3737

38-
export interface MigrationsOptions {
38+
export interface SchemaOptions {
3939
definitions: JSONSchema[];
4040
strict: ?boolean;
4141
deleteExtraFields: ?boolean;

src/Security/CheckGroups/CheckGroupDatabase.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import Config from '../../Config';
88
import Parse from 'parse/node';
99

1010
/**
11-
* The security checks group for Parse Server configuration.
12-
* Checks common Parse Server parameters such as access keys.
13-
*/
11+
* The security checks group for Parse Server configuration.
12+
* Checks common Parse Server parameters such as access keys.
13+
*/
1414
class CheckGroupDatabase extends CheckGroup {
1515
setName() {
1616
return 'Database';
@@ -23,8 +23,7 @@ class CheckGroupDatabase extends CheckGroup {
2323
new Check({
2424
title: 'Secure database password',
2525
warning: 'The database password is insecure and vulnerable to brute force attacks.',
26-
solution:
27-
'Choose a longer and/or more complex password with a combination of upper- and lowercase characters, numbers and special characters.',
26+
solution: 'Choose a longer and/or more complex password with a combination of upper- and lowercase characters, numbers and special characters.',
2827
check: () => {
2928
const password = databaseUrl.match(/\/\/\S+:(\S+)@/)[1];
3029
const hasUpperCase = /[A-Z]/.test(password);

src/Security/CheckGroups/CheckGroupServerConfig.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import Config from '../../Config';
88
import Parse from 'parse/node';
99

1010
/**
11-
* The security checks group for Parse Server configuration.
12-
* Checks common Parse Server parameters such as access keys.
13-
*/
11+
* The security checks group for Parse Server configuration.
12+
* Checks common Parse Server parameters such as access keys.
13+
*/
1414
class CheckGroupServerConfig extends CheckGroup {
1515
setName() {
1616
return 'Parse Server Configuration';
@@ -21,8 +21,7 @@ class CheckGroupServerConfig extends CheckGroup {
2121
new Check({
2222
title: 'Secure master key',
2323
warning: 'The Parse Server master key is insecure and vulnerable to brute force attacks.',
24-
solution:
25-
'Choose a longer and/or more complex master key with a combination of upper- and lowercase characters, numbers and special characters.',
24+
solution: 'Choose a longer and/or more complex master key with a combination of upper- and lowercase characters, numbers and special characters.',
2625
check: () => {
2726
const masterKey = config.masterKey;
2827
const hasUpperCase = /[A-Z]/.test(masterKey);
@@ -42,7 +41,7 @@ class CheckGroupServerConfig extends CheckGroup {
4241
new Check({
4342
title: 'Security log disabled',
4443
warning: 'Security checks in logs may expose vulnerabilities to anyone access to logs.',
45-
solution: "Change Parse Server configuration to 'security.enableCheckLog: false'.",
44+
solution: 'Change Parse Server configuration to \'security.enableCheckLog: false\'.',
4645
check: () => {
4746
if (config.security && config.security.enableCheckLog) {
4847
throw 1;
@@ -51,9 +50,8 @@ class CheckGroupServerConfig extends CheckGroup {
5150
}),
5251
new Check({
5352
title: 'Client class creation disabled',
54-
warning:
55-
'Attackers are allowed to create new classes without restriction and flood the database.',
56-
solution: "Change Parse Server configuration to 'allowClientClassCreation: false'.",
53+
warning: 'Attackers are allowed to create new classes without restriction and flood the database.',
54+
solution: 'Change Parse Server configuration to \'allowClientClassCreation: false\'.',
5755
check: () => {
5856
if (config.allowClientClassCreation || config.allowClientClassCreation == null) {
5957
throw 1;

0 commit comments

Comments
 (0)