Compare commits

...
22 Commits
Author SHA1 Message Date
Natan Rolnik 0958b15297 Changelog 2.3.2 (#3343)
* Updates package.json: version to 2.3.2

* Updates Changelog for 2.3.2
2017-01-09 15:46:34 +02:00
Lenart Rudel 5d9dbea07b Add parseFrameURL for masking user-facing pages (#3267)
* Add parseFrameURL for masking user-facing pages.

Allow users to specify a different address which is used to mask parse
requests for verifying email and resetting password. This is how Parse.com
used to allow customers to gain control over page content, styling etc.

On the destination page javascript is used to check the link in the request
and embed the parse server page using IFRAME.

* Fix code indentation

* Rename method for building link and pass config to it.

* Add customPages options to README.md.

* Add tests for parseFrameURL email link building, and parseFrameURL option.

* Add parseFrameURL for masking user-facing pages.

Allow users to specify a different address which is used to mask parse
requests for verifying email and resetting password. This is how Parse.com
used to allow customers to gain control over page content, styling etc.

On the destination page javascript is used to check the link in the request
and embed the parse server page using IFRAME.

* Fix code indentation

* Rename method for building link and pass config to it.

* Add customPages options to README.md.

* Don't Object.assign to defaultConfiguration global
2017-01-08 10:56:57 -08:00
Bhaskar Reddy Yasa f331f6644c Add support for regex string for password policy validatorPattern setting (#3331) 2017-01-08 07:12:44 -08:00
David Starke df029b82eb LiveQuery should match subobjects with dot notation (#3322)
* LiveQuery should match subobjects with dot notation

* one additional test case
2017-01-06 17:06:25 -08:00
Arthur Cinader edba550cf6 winston-daily-roate-file-1.4.2 makes a change where timestamp is not on by default anymore. (#3335)
see: https://github.com/winstonjs/winston-daily-rotate-file/commit/aa28f522718778fe8210ced1090353ffc29c6f31

pretty incredible that they would release this as a patch release, but my fix is solid and defensive

chore(package): update winston-daily-rotate-file to version 1.4.2

https://greenkeeper.io/
2017-01-06 16:32:03 -08:00
Greenkeeper c7775eb0b9 chore(package): update cross-env to version 3.1.4 (#3308)
https://greenkeeper.io/
2017-01-06 14:50:09 -08:00
Greenkeeper 0b44820a7d chore(package): update winston-daily-rotate-file to version 1.4.1 (#3317)
https://greenkeeper.io/
2017-01-06 13:20:12 -08:00
Greenkeeper b7322064d8 chore(package): update pg-promise to version 5.5.0 (#3224)
https://greenkeeper.io/
2017-01-04 09:41:21 -05:00
jeacott1 ac3f5cd686 this function was extremely slow. (#3264)
this fix reduces the time to process 165k installations
from 282738.599ms (almost 5 minutes of total server lockup!)
to just 16.283ms!
2017-01-04 09:27:47 -05:00
Greenkeeper 9fd34fd25e chore(package): update bcryptjs to version 2.4.0 (#3225)
https://greenkeeper.io/
2017-01-03 16:42:59 -06:00
Greenkeeper 7ff3a3813d chore(package): update babel-core to version 6.21.0 (#3249)
https://greenkeeper.io/
2017-01-02 15:04:18 -06:00
Greenkeeper 754f5d0505 chore(package): update babel-plugin-transform-flow-strip-types to version 6.21.0 (#3250)
https://greenkeeper.io/
2017-01-02 14:40:30 -06:00
Greenkeeper 0e8c3c2457 chore(package): update multer to version 1.2.1 (#3276)
https://greenkeeper.io/
2017-01-02 14:09:24 -06:00
Greenkeeper 423a50133a chore(package): update lodash to version 4.17.4 (#3303)
https://greenkeeper.io/
2017-01-02 13:25:37 -06:00
Greenkeeper a3d416de4a chore(package): update bcrypt to version 1.0.2 (#3304)
https://greenkeeper.io/
2017-01-01 12:01:59 -06:00
Greenkeeper a2dfcc8da8 chore(package): update babel-core to version 6.20.0 (#3216)
https://greenkeeper.io/
2017-01-01 08:44:38 -06:00
Arthur Cinader f4734a65c0 fix trivial typo (#3238) 2016-12-14 14:28:23 -08:00
Florent Vilmart aa071e034e Changelog 2.3.1 (#3213)
* Update CHANGELOG.md

* Update package.json
2016-12-08 14:30:59 -05:00
Florent Vilmart 4f46eceacf Fixes issue affecting the auth providers (#3211)
* Reproduces error for issue 3208

* Make sure we don't override a default adapter with the options when validation function are not provided
2016-12-08 08:54:06 -08:00
Florent Vilmart 81ebbb8954 Update README.md 2016-12-08 10:16:31 -05:00
Greenkeeper e82395e268 chore(package): update bcrypt to version 1.0.1 (#3207)
https://greenkeeper.io/
2016-12-08 09:55:35 -05:00
Florent Vilmart 0159360a5d Fixes CI dist (#3206) 2016-12-07 19:57:13 -08:00
17 changed files with 347 additions and 65 deletions
+11 -10
View File
@@ -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
+21
View File
@@ -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)
+3 -2
View File
@@ -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
View File
@@ -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
View File
+33
View File
@@ -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);
});
});
+63 -7
View File
@@ -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(() => {
+62
View File
@@ -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);
});
});
+59
View File
@@ -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)
})
})
})
});
+3 -1
View File
@@ -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
View File
@@ -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;
+2 -2
View File
@@ -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
View File
@@ -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`;
}
+1 -1
View File
@@ -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,
+15 -2
View File
@@ -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;
+7
View File
@@ -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++) {
+7 -6
View File
@@ -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) {