mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0958b15297 | ||
|
|
5d9dbea07b | ||
|
|
f331f6644c | ||
|
|
df029b82eb | ||
|
|
edba550cf6 | ||
|
|
c7775eb0b9 | ||
|
|
0b44820a7d | ||
|
|
b7322064d8 | ||
|
|
ac3f5cd686 | ||
|
|
9fd34fd25e | ||
|
|
7ff3a3813d | ||
|
|
754f5d0505 | ||
|
|
0e8c3c2457 | ||
|
|
423a50133a | ||
|
|
a3d416de4a | ||
|
|
a2dfcc8da8 | ||
|
|
f4734a65c0 | ||
|
|
aa071e034e | ||
|
|
4f46eceacf | ||
|
|
81ebbb8954 | ||
|
|
e82395e268 | ||
|
|
0159360a5d |
+11
-10
@@ -45,6 +45,17 @@ after_success:
|
||||
- export $(cat resources/.to_export_back)
|
||||
|
||||
deploy:
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email:
|
||||
secure: T3LwSJFcu632SDfCoavcKL39NktWLEOOFzonAjeHAglmMyDp9hvU8xHwQ4JJy2CRA03c+ezAe2gH3Va+jfxvh1JtFUan+p88vaCHHKuNwPSp4WZBPt1ZTDmG+ACG6j+ZWHK+eP7hLvtlCi/M9/v4/bVojpm7v01LLiM+MRhnE9E7eLemQC4zb6RVtf2oGQ3mX51kMeya218kEm5bsQPpRJElI9jwletFa8qYK5AFgaCHpll059RRHQTTE9MTNcN58P/Kf6Qb3KGpKNoaHTXoOD4U6LcXLWvdHONVB6AzGefxg2b/dvtaO4nd5lDooMBR3u9kWpopXkyAuf+fO/8je9JTxO6CDUtDjHRcR2mCEXWX6rculIAbNXOv1ccRYCTQ8aFtYnFlMSb1+QIAdyT6HHmeT5ktk3+XJRuEv0PJJvqHTo+j7tPngjiv5sPNutgGFlOFO90omTbzEkIT/D/LsgXbWm2QKYWejbLiuSINg+FiFiZN90doCf4aCpm2y1Q/9GzvK+eXcpqzPCGxOykE7EuPZK3+S/ZU2oaWL2uSpbKPtC0qxebrneR307aVEa7C85HCkTMMBzE4tjUr8h5HhLqtWvqmDcnBo3PfQhf9cEO6jQLd3qhEVJmcwKz/yOCfgPXlCbRqiKk7j94perpNCIalXtMI9ySYjJNjHsB4600=
|
||||
api_key:
|
||||
secure: WLjhHVAdUkDy6UdNWveTpZqphw9olN0alCpyPpU0cJjlN/hk7YxPP+YHOSVnMZaRZoHM0LL11jPKLf970nymBfvJHDWxKk71c/5xyTX8pBtSxmGmGy23a5g2VrHVMd2JxsI7NEAH500tlFz+01t4E3Steo0NpAkHR3Q51kj01gQy8IumsksfhRc9uvjZ7zjm99Yk4L+cxyei79v4esfpx7Bgm0HTfAAAG/19F+r0hvvFiV517SREDU/YtcX/rIjVepEy1uiLKEohnmYtVIRSA3Hjh6VlHVhdj2WDd/dYrxi/Ioysq2zOM9pZVLamO9asB1e6JrTEipWE9jSZXxsbnfFzuNLxcOjKv1wd3OzQKF/7pGKGiWRTv5Xm19D/FrXoE/ULs6bHcC/Ke8Gs2RxPlOCdvAFehCkyf5P4HOPCQdR7o8Yiuvt+5JWKBflElNbSd4nWgwUOo3Yv8vC4Vj53fwmM+Uqfu3IgYFWktYxCm4RWIKMfB/gtmjcF6QYFfjvEMwAvRfBV81kuynCsnubXWzQeBE/b3JOhBfpGciKCjKfy+tS6bZfFjCtQV98hMMiCPre8Y7PahHDYc65wU9Ake+ZE+dDaSbeV3DZ5JeifLJHzKW2J2dWeRANkOaiSwO9VBC9/rht5ulK5qQ1pB2+sKDToAaiKy6RMlB/HgIoYqsU=
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
|
||||
repo: ParsePlatform/parse-server
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: ./resources/npm-git.sh
|
||||
@@ -58,13 +69,3 @@ deploy:
|
||||
tags: true
|
||||
all_branches: true
|
||||
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
|
||||
- provider: npm
|
||||
email:
|
||||
secure: T3LwSJFcu632SDfCoavcKL39NktWLEOOFzonAjeHAglmMyDp9hvU8xHwQ4JJy2CRA03c+ezAe2gH3Va+jfxvh1JtFUan+p88vaCHHKuNwPSp4WZBPt1ZTDmG+ACG6j+ZWHK+eP7hLvtlCi/M9/v4/bVojpm7v01LLiM+MRhnE9E7eLemQC4zb6RVtf2oGQ3mX51kMeya218kEm5bsQPpRJElI9jwletFa8qYK5AFgaCHpll059RRHQTTE9MTNcN58P/Kf6Qb3KGpKNoaHTXoOD4U6LcXLWvdHONVB6AzGefxg2b/dvtaO4nd5lDooMBR3u9kWpopXkyAuf+fO/8je9JTxO6CDUtDjHRcR2mCEXWX6rculIAbNXOv1ccRYCTQ8aFtYnFlMSb1+QIAdyT6HHmeT5ktk3+XJRuEv0PJJvqHTo+j7tPngjiv5sPNutgGFlOFO90omTbzEkIT/D/LsgXbWm2QKYWejbLiuSINg+FiFiZN90doCf4aCpm2y1Q/9GzvK+eXcpqzPCGxOykE7EuPZK3+S/ZU2oaWL2uSpbKPtC0qxebrneR307aVEa7C85HCkTMMBzE4tjUr8h5HhLqtWvqmDcnBo3PfQhf9cEO6jQLd3qhEVJmcwKz/yOCfgPXlCbRqiKk7j94perpNCIalXtMI9ySYjJNjHsB4600=
|
||||
api_key:
|
||||
secure: WLjhHVAdUkDy6UdNWveTpZqphw9olN0alCpyPpU0cJjlN/hk7YxPP+YHOSVnMZaRZoHM0LL11jPKLf970nymBfvJHDWxKk71c/5xyTX8pBtSxmGmGy23a5g2VrHVMd2JxsI7NEAH500tlFz+01t4E3Steo0NpAkHR3Q51kj01gQy8IumsksfhRc9uvjZ7zjm99Yk4L+cxyei79v4esfpx7Bgm0HTfAAAG/19F+r0hvvFiV517SREDU/YtcX/rIjVepEy1uiLKEohnmYtVIRSA3Hjh6VlHVhdj2WDd/dYrxi/Ioysq2zOM9pZVLamO9asB1e6JrTEipWE9jSZXxsbnfFzuNLxcOjKv1wd3OzQKF/7pGKGiWRTv5Xm19D/FrXoE/ULs6bHcC/Ke8Gs2RxPlOCdvAFehCkyf5P4HOPCQdR7o8Yiuvt+5JWKBflElNbSd4nWgwUOo3Yv8vC4Vj53fwmM+Uqfu3IgYFWktYxCm4RWIKMfB/gtmjcF6QYFfjvEMwAvRfBV81kuynCsnubXWzQeBE/b3JOhBfpGciKCjKfy+tS6bZfFjCtQV98hMMiCPre8Y7PahHDYc65wU9Ake+ZE+dDaSbeV3DZ5JeifLJHzKW2J2dWeRANkOaiSwO9VBC9/rht5ulK5qQ1pB2+sKDToAaiKy6RMlB/HgIoYqsU=
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
condition: "$MONGODB_VERSION = '3.0.8'"
|
||||
repo: ParsePlatform/parse-server
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### 2.3.2
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.1...2.3.2)
|
||||
|
||||
#### New features
|
||||
* Add parseFrameURL for masking user-facing pages (#3267), thanks to [Lenart Rudel](https://github.com/lenart)
|
||||
|
||||
#### Bug fixes
|
||||
* Fix Parse-Server to work with winston-daily-rotate-1.4.2 (#3335), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
#### Improvements
|
||||
* Add support for regex string for password policy validatorPattern setting (#3331), thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
|
||||
* LiveQuery should match subobjects with dot notation (#3322), thanks to [David Starke](https://github.com/dstarke)
|
||||
* Reduce time to process high number of installations for push (#3264), thanks to [jeacott1](https://github.com/jeacott1)
|
||||
* Fix trivial typo in error message (#3238), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
|
||||
### 2.3.1
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.0...2.3.1)
|
||||
|
||||
A major issue was introduced when refactoring the authentication modules.
|
||||
This release addresses only that issue.
|
||||
|
||||
### 2.3.0
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.2.25...2.3.0)
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
|
||||
* `fileKey` - For migrated apps, this is necessary to provide access to files already hosted on Parse.
|
||||
* `allowClientClassCreation` - Set to false to disable client class creation. Defaults to true.
|
||||
* `enableAnonymousUsers` - Set to false to disable anonymous users. Defaults to true.
|
||||
* `oauth` - Used to configure support for [3rd party authentication](https://github.com/ParsePlatform/parse-server/wiki/OAuth).
|
||||
* `auth` - Used to configure support for [3rd party authentication](https://github.com/ParsePlatform/parse-server/wiki/OAuth--and-Custom-Authentication).
|
||||
* `facebookAppIds` - An array of valid Facebook application IDs that users may authenticate with.
|
||||
* `mountPath` - Mount path for the server. Defaults to `/parse`.
|
||||
* `filesAdapter` - The default behavior (GridStore) can be changed by creating an adapter class (see [`FilesAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Files/FilesAdapter.js)).
|
||||
@@ -216,6 +216,7 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
|
||||
* `revokeSessionOnPasswordReset` - When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.
|
||||
* `accountLockout` - Lock account when a malicious user is attempting to determine an account password by trial and error.
|
||||
* `passwordPolicy` - Optional password policy rules to enforce.
|
||||
* `customPages` - A hash with urls to override email verification links, password reset links and specify frame url for masking user-facing pages. Available keys: `parseFrameURL`, `invalidLink`, `choosePassword`, `passwordResetSuccess`, `verifyEmailSuccess`.
|
||||
|
||||
##### Logging
|
||||
|
||||
@@ -281,7 +282,7 @@ var server = ParseServer({
|
||||
passwordPolicy: {
|
||||
// Two optional settings to enforce strong passwords. Either one or both can be specified.
|
||||
// If both are specified, both checks must pass to accept the password
|
||||
// 1. a RegExp representing the pattern to enforce
|
||||
// 1. a RegExp object or a regex string representing the pattern to enforce
|
||||
validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/, // enforce password with at least 8 char with at least 1 lower case, 1 upper case and 1 digit
|
||||
// 2. a callback function to be invoked to validate the password
|
||||
validatorCallback: (password) => { return validatePassword(password) },
|
||||
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.2",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -18,42 +18,42 @@
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"bcryptjs": "2.3.0",
|
||||
"bcryptjs": "2.4.0",
|
||||
"body-parser": "1.15.2",
|
||||
"commander": "2.9.0",
|
||||
"deepcopy": "0.6.3",
|
||||
"express": "4.14.0",
|
||||
"intersect": "1.0.1",
|
||||
"lodash": "4.17.2",
|
||||
"lodash": "4.17.4",
|
||||
"lru-cache": "4.0.2",
|
||||
"mime": "1.3.4",
|
||||
"mongodb": "2.2.10",
|
||||
"multer": "1.2.0",
|
||||
"multer": "1.2.1",
|
||||
"parse": "1.9.2",
|
||||
"parse-server-fs-adapter": "1.0.1",
|
||||
"parse-server-push-adapter": "1.1.0",
|
||||
"parse-server-s3-adapter": "1.0.6",
|
||||
"parse-server-simple-mailgun-adapter": "1.0.0",
|
||||
"pg-promise": "5.4.5",
|
||||
"pg-promise": "5.5.0",
|
||||
"redis": "2.6.3",
|
||||
"request": "2.79.0",
|
||||
"semver": "5.2.0",
|
||||
"tv4": "1.2.7",
|
||||
"winston": "2.3.0",
|
||||
"winston-daily-rotate-file": "1.4.0",
|
||||
"winston-daily-rotate-file": "1.4.2",
|
||||
"ws": "1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "6.18.0",
|
||||
"babel-core": "6.18.2",
|
||||
"babel-core": "6.21.0",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-plugin-syntax-flow": "6.13.0",
|
||||
"babel-plugin-transform-flow-strip-types": "6.18.0",
|
||||
"babel-plugin-transform-flow-strip-types": "6.21.0",
|
||||
"babel-preset-es2015": "6.14.0",
|
||||
"babel-preset-stage-0": "6.5.0",
|
||||
"babel-register": "6.18.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"cross-env": "3.1.3",
|
||||
"cross-env": "3.1.4",
|
||||
"deep-diff": "0.3.4",
|
||||
"eslint": "^3.10.2",
|
||||
"eslint-plugin-flowtype": "^2.25.0",
|
||||
@@ -84,6 +84,6 @@
|
||||
"parse-server": "./bin/parse-server"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "1.0.0"
|
||||
"bcrypt": "1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
@@ -203,6 +203,13 @@ describe('AuthenticationProviers', function() {
|
||||
expect(typeof authenticatonHandler.getValidatorForProvider).toBe('function');
|
||||
}
|
||||
|
||||
function validateAuthenticationAdapter(authAdapter) {
|
||||
expect(authAdapter).not.toBeUndefined();
|
||||
if (!authAdapter) { return; }
|
||||
expect(typeof authAdapter.validateAuthData).toBe('function');
|
||||
expect(typeof authAdapter.validateAppId).toBe('function');
|
||||
}
|
||||
|
||||
it('properly loads custom adapter', (done) => {
|
||||
var validAuthData = {
|
||||
id: 'hello',
|
||||
@@ -279,4 +286,30 @@ describe('AuthenticationProviers', function() {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('properly loads a default adapter with options', () => {
|
||||
const options = {
|
||||
facebook: {
|
||||
appIds: ['a', 'b']
|
||||
}
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter('facebook', options);
|
||||
validateAuthenticationAdapter(adapter);
|
||||
expect(appIds).toEqual(['a', 'b']);
|
||||
expect(providerOptions).toEqual(options.facebook);
|
||||
});
|
||||
|
||||
it('properly loads a custom adapter with options', () => {
|
||||
const options = {
|
||||
custom: {
|
||||
validateAppId: () => {},
|
||||
validateAuthData: () => {},
|
||||
appIds: ['a', 'b']
|
||||
}
|
||||
};
|
||||
const { adapter, appIds, providerOptions } = authenticationLoader.loadAuthAdapter('custom', options);
|
||||
validateAuthenticationAdapter(adapter);
|
||||
expect(appIds).toEqual(['a', 'b']);
|
||||
expect(providerOptions).toEqual(options.custom);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -147,14 +147,14 @@ describe("Password Policy: ", () => {
|
||||
reconfigureServer({
|
||||
appName: 'passwordPolicy',
|
||||
passwordPolicy: {
|
||||
validatorPattern: "abc" // string is not a valid setting
|
||||
validatorPattern: 1234 // number is not a valid setting
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
}).then(() => {
|
||||
fail('passwordPolicy.validatorPattern type test failed');
|
||||
done();
|
||||
}).catch(err => {
|
||||
expect(err).toEqual('passwordPolicy.validatorPattern must be a RegExp.');
|
||||
expect(err).toEqual('passwordPolicy.validatorPattern must be a regex string or RegExp object.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -197,6 +197,28 @@ describe("Password Policy: ", () => {
|
||||
})
|
||||
});
|
||||
|
||||
it('signup should fail if password does not conform to the policy enforced using validatorPattern string', (done) => {
|
||||
const user = new Parse.User();
|
||||
reconfigureServer({
|
||||
appName: 'passwordPolicy',
|
||||
passwordPolicy: {
|
||||
validatorPattern: "^.{8,}" // password should contain at least 8 char
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
}).then(() => {
|
||||
user.setUsername("user1");
|
||||
user.setPassword("less");
|
||||
user.set('email', 'user1@parse.com');
|
||||
user.signUp().then(() => {
|
||||
fail('Should have failed as password does not conform to the policy.');
|
||||
done();
|
||||
}).catch((error) => {
|
||||
expect(error.code).toEqual(142);
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it('signup should succeed if password conforms to the policy enforced using validatorPattern', (done) => {
|
||||
const user = new Parse.User();
|
||||
reconfigureServer({
|
||||
@@ -231,6 +253,40 @@ describe("Password Policy: ", () => {
|
||||
})
|
||||
});
|
||||
|
||||
it('signup should succeed if password conforms to the policy enforced using validatorPattern string', (done) => {
|
||||
const user = new Parse.User();
|
||||
reconfigureServer({
|
||||
appName: 'passwordPolicy',
|
||||
passwordPolicy: {
|
||||
validatorPattern: "[!@#$]+" // password should contain at least one special char
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
}).then(() => {
|
||||
user.setUsername("user1");
|
||||
user.setPassword("p@sswrod");
|
||||
user.set('email', 'user1@parse.com');
|
||||
user.signUp().then(() => {
|
||||
Parse.User.logOut().then(() => {
|
||||
Parse.User.logIn("user1", "p@sswrod").then(function () {
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
fail("Should be able to login");
|
||||
done();
|
||||
});
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
fail('logout should have succeeded');
|
||||
done();
|
||||
});
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
fail('Signup should have succeeded as password conforms to the policy.');
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it('signup should fail if password does not conform to the policy enforced using validatorCallback', (done) => {
|
||||
const user = new Parse.User();
|
||||
reconfigureServer({
|
||||
@@ -409,12 +465,12 @@ describe("Password Policy: ", () => {
|
||||
fail("should login with new password");
|
||||
done();
|
||||
});
|
||||
}).catch((error)=> {
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
fail("Failed to POST request password reset");
|
||||
done();
|
||||
});
|
||||
}).catch((error)=> {
|
||||
}).catch((error) => {
|
||||
jfail(error);
|
||||
fail("Failed to get the reset link");
|
||||
done();
|
||||
@@ -839,7 +895,7 @@ describe("Password Policy: ", () => {
|
||||
reconfigureServer({
|
||||
appName: 'passwordPolicy',
|
||||
passwordPolicy: {
|
||||
maxPasswordAge: 0.5/(24*60*60) // 0.5 sec
|
||||
maxPasswordAge: 0.5 / (24 * 60 * 60) // 0.5 sec
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
}).then(() => {
|
||||
@@ -880,7 +936,7 @@ describe("Password Policy: ", () => {
|
||||
reconfigureServer({
|
||||
appName: 'passwordPolicy',
|
||||
passwordPolicy: {
|
||||
maxPasswordAge: 0.5/(24*60*60) // 0.5 sec
|
||||
maxPasswordAge: 0.5 / (24 * 60 * 60) // 0.5 sec
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
}).then(() => {
|
||||
@@ -979,7 +1035,7 @@ describe("Password Policy: ", () => {
|
||||
appName: 'passwordPolicy',
|
||||
emailAdapter: emailAdapter,
|
||||
passwordPolicy: {
|
||||
maxPasswordAge: 0.5/(24*60*60) // 0.5 sec
|
||||
maxPasswordAge: 0.5 / (24 * 60 * 60) // 0.5 sec
|
||||
},
|
||||
publicServerURL: "http://localhost:8378/1"
|
||||
}).then(() => {
|
||||
|
||||
@@ -412,4 +412,66 @@ describe('matchesQuery', function() {
|
||||
expect(matchesQuery(caltrainStation, q)).toBe(false);
|
||||
expect(matchesQuery(santaClara, q)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches on subobjects with dot notation', function() {
|
||||
var message = {
|
||||
id: new Id('Message', 'O1'),
|
||||
text: "content",
|
||||
status: {x: "read", y: "delivered"}
|
||||
};
|
||||
|
||||
var q = new Parse.Query('Message');
|
||||
q.equalTo("status.x", "read");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.equalTo("status.z", "read");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.equalTo("status.x", "delivered");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.notEqualTo("status.x", "read");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.notEqualTo("status.z", "read");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.notEqualTo("status.x", "delivered");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.exists("status.x");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.exists("status.z");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.exists("nonexistent.x");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.doesNotExist("status.x");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.doesNotExist("status.z");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.doesNotExist("nonexistent.z");
|
||||
expect(matchesQuery(message, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Message');
|
||||
q.equalTo("status.x", "read");
|
||||
q.doesNotExist("status.y");
|
||||
expect(matchesQuery(message, q)).toBe(false);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
var UserController = require('../src/Controllers/UserController').UserController;
|
||||
var emailAdapter = require('./MockEmailAdapter')
|
||||
var AppCache = require('../src/cache').AppCache;
|
||||
|
||||
describe('UserController', () => {
|
||||
var user = {
|
||||
_email_verify_token: 'testToken',
|
||||
username: 'testUser',
|
||||
email: 'test@example.com'
|
||||
}
|
||||
|
||||
describe('sendVerificationEmail', () => {
|
||||
describe('parseFrameURL not provided', () => {
|
||||
it('uses publicServerURL', (done) => {
|
||||
|
||||
AppCache.put(defaultConfiguration.appId, Object.assign({}, defaultConfiguration, {
|
||||
publicServerURL: 'http://www.example.com',
|
||||
customPages: {
|
||||
parseFrameURL: undefined
|
||||
}
|
||||
}))
|
||||
|
||||
emailAdapter.sendVerificationEmail = (options) => {
|
||||
expect(options.link).toEqual('http://www.example.com/apps/test/verify_email?token=testToken&username=testUser')
|
||||
done()
|
||||
}
|
||||
|
||||
var userController = new UserController(emailAdapter, 'test', {
|
||||
verifyUserEmails: true
|
||||
})
|
||||
|
||||
userController.sendVerificationEmail(user)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseFrameURL provided', () => {
|
||||
it('uses parseFrameURL and includes the destination in the link parameter', (done) => {
|
||||
|
||||
AppCache.put(defaultConfiguration.appId, Object.assign({}, defaultConfiguration, {
|
||||
publicServerURL: 'http://www.example.com',
|
||||
customPages: {
|
||||
parseFrameURL: 'http://someother.example.com/handle-parse-iframe'
|
||||
}
|
||||
}))
|
||||
|
||||
emailAdapter.sendVerificationEmail = (options) => {
|
||||
expect(options.link).toEqual('http://someother.example.com/handle-parse-iframe?link=%2Fapps%2Ftest%2Fverify_email&token=testToken&username=testUser')
|
||||
done()
|
||||
}
|
||||
|
||||
var userController = new UserController(emailAdapter, 'test', {
|
||||
verifyUserEmails: true
|
||||
})
|
||||
|
||||
userController.sendVerificationEmail(user)
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -12,7 +12,8 @@ describe("Custom Pages, Email Verification, Password Reset", () => {
|
||||
invalidLink: "myInvalidLink",
|
||||
verifyEmailSuccess: "myVerifyEmailSuccess",
|
||||
choosePassword: "myChoosePassword",
|
||||
passwordResetSuccess: "myPasswordResetSuccess"
|
||||
passwordResetSuccess: "myPasswordResetSuccess",
|
||||
parseFrameURL: "http://example.com/handle-parse-iframe"
|
||||
},
|
||||
publicServerURL: "https://my.public.server.com/1"
|
||||
})
|
||||
@@ -22,6 +23,7 @@ describe("Custom Pages, Email Verification, Password Reset", () => {
|
||||
expect(config.verifyEmailSuccessURL).toEqual("myVerifyEmailSuccess");
|
||||
expect(config.choosePasswordURL).toEqual("myChoosePassword");
|
||||
expect(config.passwordResetSuccessURL).toEqual("myPasswordResetSuccess");
|
||||
expect(config.parseFrameURL).toEqual("http://example.com/handle-parse-iframe");
|
||||
expect(config.verifyEmailURL).toEqual("https://my.public.server.com/1/apps/test/verify_email");
|
||||
expect(config.requestResetPasswordURL).toEqual("https://my.public.server.com/1/apps/test/request_password_reset");
|
||||
done();
|
||||
|
||||
+38
-22
@@ -55,6 +55,36 @@ function authDataValidator(adapter, appIds, options) {
|
||||
}
|
||||
}
|
||||
|
||||
function loadAuthAdapter(provider, authOptions) {
|
||||
const defaultAdapter = providers[provider];
|
||||
const adapter = Object.assign({}, defaultAdapter);
|
||||
const providerOptions = authOptions[provider];
|
||||
|
||||
if (!defaultAdapter && !providerOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const appIds = providerOptions ? providerOptions.appIds : undefined;
|
||||
|
||||
// Try the configuration methods
|
||||
if (providerOptions) {
|
||||
const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions);
|
||||
if (optionalAdapter) {
|
||||
['validateAuthData', 'validateAppId'].forEach((key) => {
|
||||
if (optionalAdapter[key]) {
|
||||
adapter[key] = optionalAdapter[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!adapter.validateAuthData || !adapter.validateAppId) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {adapter, appIds, providerOptions};
|
||||
}
|
||||
|
||||
module.exports = function(authOptions = {}, enableAnonymousUsers = true) {
|
||||
let _enableAnonymousUsers = enableAnonymousUsers;
|
||||
const setEnableAnonymousUsers = function(enable) {
|
||||
@@ -67,33 +97,19 @@ module.exports = function(authOptions = {}, enableAnonymousUsers = true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultAdapter = providers[provider];
|
||||
let adapter = defaultAdapter;
|
||||
const providerOptions = authOptions[provider];
|
||||
|
||||
if (!defaultAdapter && !providerOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const appIds = providerOptions ? providerOptions.appIds : undefined;
|
||||
|
||||
// Try the configuration methods
|
||||
if (providerOptions) {
|
||||
const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions);
|
||||
if (optionalAdapter) {
|
||||
adapter = optionalAdapter;
|
||||
}
|
||||
}
|
||||
|
||||
if (!adapter.validateAuthData || !adapter.validateAppId) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
adapter,
|
||||
appIds,
|
||||
providerOptions
|
||||
} = loadAuthAdapter(provider, authOptions);
|
||||
|
||||
return authDataValidator(adapter, appIds, providerOptions);
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
getValidatorForProvider,
|
||||
setEnableAnonymousUsers,
|
||||
setEnableAnonymousUsers
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.loadAuthAdapter = loadAuthAdapter;
|
||||
|
||||
@@ -21,12 +21,12 @@ function updateTransports(options) {
|
||||
Object.assign({}, {
|
||||
filename: 'parse-server.info',
|
||||
name: 'parse-server',
|
||||
}, options));
|
||||
}, options, { timestamp: true }));
|
||||
transports['parse-server-error'] = new (DailyRotateFile)(
|
||||
Object.assign({}, {
|
||||
filename: 'parse-server.err',
|
||||
name: 'parse-server-error',
|
||||
}, options, { level: 'error'}));
|
||||
}, options, { level: 'error', timestamp: true }));
|
||||
}
|
||||
|
||||
transports.console = new (winston.transports.Console)(
|
||||
|
||||
+12
-2
@@ -127,10 +127,16 @@ export class Config {
|
||||
throw 'passwordPolicy.resetTokenValidityDuration must be a positive number';
|
||||
}
|
||||
|
||||
if(passwordPolicy.validatorPattern && !(passwordPolicy.validatorPattern instanceof RegExp)) {
|
||||
throw 'passwordPolicy.validatorPattern must be a RegExp.';
|
||||
if(passwordPolicy.validatorPattern){
|
||||
if(typeof(passwordPolicy.validatorPattern) === 'string') {
|
||||
passwordPolicy.validatorPattern = new RegExp(passwordPolicy.validatorPattern);
|
||||
}
|
||||
else if(!(passwordPolicy.validatorPattern instanceof RegExp)){
|
||||
throw 'passwordPolicy.validatorPattern must be a regex string or RegExp object.';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(passwordPolicy.validatorCallback && typeof passwordPolicy.validatorCallback !== 'function') {
|
||||
throw 'passwordPolicy.validatorCallback must be a function.';
|
||||
}
|
||||
@@ -240,6 +246,10 @@ export class Config {
|
||||
return this.customPages.passwordResetSuccess || `${this.publicServerURL}/apps/password_reset_success.html`;
|
||||
}
|
||||
|
||||
get parseFrameURL() {
|
||||
return this.customPages.parseFrameURL;
|
||||
}
|
||||
|
||||
get verifyEmailURL() {
|
||||
return `${this.publicServerURL}/apps/${this.applicationId}/verify_email`;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ export class LoggerController extends AdaptableController {
|
||||
getLogs(options= {}) {
|
||||
if (!this.adapter) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Logger adapter is not availabe');
|
||||
'Logger adapter is not available');
|
||||
}
|
||||
if (typeof this.adapter.query !== 'function') {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
|
||||
@@ -119,7 +119,8 @@ export class UserController extends AdaptableController {
|
||||
// We may need to fetch the user in case of update email
|
||||
this.getUserIfNeeded(user).then((user) => {
|
||||
const username = encodeURIComponent(user.username);
|
||||
const link = `${this.config.verifyEmailURL}?token=${token}&username=${username}`;
|
||||
|
||||
const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config);
|
||||
const options = {
|
||||
appName: this.config.appName,
|
||||
link: link,
|
||||
@@ -153,8 +154,8 @@ export class UserController extends AdaptableController {
|
||||
.then(user => {
|
||||
const token = encodeURIComponent(user._perishable_token);
|
||||
const username = encodeURIComponent(user.username);
|
||||
const link = `${this.config.requestResetPasswordURL}?token=${token}&username=${username}`
|
||||
|
||||
const link = buildEmailLink(this.config.requestResetPasswordURL, username, token, this.config);
|
||||
const options = {
|
||||
appName: this.config.appName,
|
||||
link: link,
|
||||
@@ -215,4 +216,16 @@ function updateUserPassword(userId, password, config) {
|
||||
});
|
||||
}
|
||||
|
||||
function buildEmailLink(destination, username, token, config) {
|
||||
const usernameAndToken = `token=${token}&username=${username}`
|
||||
|
||||
if (config.parseFrameURL) {
|
||||
const destinationWithoutHost = destination.replace(config.publicServerURL, '');
|
||||
|
||||
return `${config.parseFrameURL}?link=${encodeURIComponent(destinationWithoutHost)}&${usernameAndToken}`;
|
||||
} else {
|
||||
return `${destination}?${usernameAndToken}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UserController;
|
||||
|
||||
@@ -133,6 +133,13 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
if (constraints === null) {
|
||||
return false;
|
||||
}
|
||||
if(key.indexOf(".") >= 0){
|
||||
// Key references a subobject
|
||||
var keyComponents = key.split(".");
|
||||
var subObjectKey = keyComponents[0];
|
||||
var keyRemainder = keyComponents.slice(1).join(".");
|
||||
return matchesKeyConstraints(object[subObjectKey] || {}, keyRemainder, constraints);
|
||||
}
|
||||
var i;
|
||||
if (key === '$or') {
|
||||
for (i = 0; i < constraints.length; i++) {
|
||||
|
||||
@@ -5,14 +5,15 @@ const PUSH_STATUS_COLLECTION = '_PushStatus';
|
||||
const JOB_STATUS_COLLECTION = '_JobStatus';
|
||||
|
||||
export function flatten(array) {
|
||||
return array.reduce((memo, element) => {
|
||||
if (Array.isArray(element)) {
|
||||
memo = memo.concat(flatten(element));
|
||||
var flattened = [];
|
||||
for(var i = 0; i < array.length; i++) {
|
||||
if(Array.isArray(array[i])) {
|
||||
flattened = flattened.concat(flatten(array[i]));
|
||||
} else {
|
||||
memo = memo.concat(element);
|
||||
flattened.push(array[i]);
|
||||
}
|
||||
return memo;
|
||||
}, []);
|
||||
}
|
||||
return flattened;
|
||||
}
|
||||
|
||||
function statusHandler(className, database) {
|
||||
|
||||
Reference in New Issue
Block a user