-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support custom AccountsClient instances (#4)
- Loading branch information
Tomasz Przytuła
committed
Mar 13, 2018
1 parent
b0ff369
commit dcad01b
Showing
25 changed files
with
989 additions
and
450 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"extends": [ | ||
"airbnb-base", | ||
"plugin:meteor/recommended" | ||
], | ||
"plugins": [ | ||
"meteor" | ||
], | ||
"env": { | ||
"meteor": true | ||
}, | ||
"settings": { | ||
"import/resolver": "meteor" | ||
}, | ||
"parser": "babel-eslint", | ||
"globals": { | ||
"Meteor": true, | ||
"it": true, | ||
"describe": true | ||
}, | ||
"rules": { | ||
"comma-dangle": [ | ||
0 | ||
], | ||
"indent": [ | ||
2, | ||
4, | ||
{ | ||
"SwitchCase": 1 | ||
} | ||
], | ||
"import/extensions": [ | ||
"off", | ||
"never" | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
.idea | ||
.npm | ||
.vscode | ||
node_modules | ||
node_modules | ||
package-lock.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# AccountsClient | ||
|
||
## Custom AccountsClient in Meteor Apps | ||
|
||
#### Wait why? | ||
|
||
Imagine an example situation where you decided to split your Meteor server into two separate ones to reduce the overload. You found it pretty convenient to use one of them only for serving HCP where the separate one will handle all the logic. | ||
|
||
Another example could be an android app in the Google Play store where upon start you could decide between connecting to the *NA* or *EU* server. | ||
|
||
#### How ? | ||
To achieve the first example you will point all the client apps to the HCP server and connect to the separate one using: | ||
|
||
```js | ||
const remoteConnection = DDP.connect('127.0.0.1:4000'); | ||
``` | ||
By now you can easily start using methods from the new connection the same way as you are doing it on the main: | ||
```js | ||
remoteConnection.call('delayApocalypse', 1000, (error) => { | ||
if (error) { | ||
console.error('Could not delay the apocalypse:', error); | ||
} | ||
}) | ||
``` | ||
|
||
However migrating the accounts system is a bit more tricky. Upon using the default accounts methods the app's clients will try to log in to the main server by the default (which was supposed to be HCP only). | ||
|
||
The first step for migration is to create a new instance of AccountsClient | ||
|
||
```js | ||
const accountsClient = new AccountsClient({ connection: remoteConnection }); | ||
``` | ||
|
||
Unfortunately if you want to have a method like `loginWithPassword` then you have to implement it yourself the same way as it's done for the main accounts system [Source](https://github.com/meteor/meteor/blob/46257bad264bf089e35e0fe35494b51fe5849c7b/packages/accounts-password/password_client.js#L33) | ||
|
||
But don't worry! Using tprzytula:remember-me you don't have to worry about that. | ||
|
||
## Switching to the custom AccountsClient in tprzytula:remember-me | ||
|
||
Using this dependency the login logic always stays the same no matter of which AccountsClient system you are currently using. You can switch the accounts system at any point during your app lifetime. After you will be done with the AccountsClient configuration the only thing you need to do is to pass the instance to *changeAccountsSystem* method and voila! | ||
|
||
### Example: | ||
|
||
##### Configuration: | ||
|
||
To let the dependency know that you have and want to use a separate custom account system you need to pass the instance to the `changeAccountsSystem` method. | ||
|
||
```js | ||
import { AccountsClient } from 'meteor/accounts-base'; | ||
import RememberMe from 'meteor/tprzytula:remember-me'; | ||
|
||
Meteor.remoteConnection = DDP.connect('127.0.0.1:4000'); // Meteor's server for accounts | ||
Meteor.remoteUsers = new AccountsClient({ connection: Meteor.remoteConnection }); | ||
|
||
RememberMe.changeAccountsSystem(Meteor.remoteUsers); | ||
|
||
``` | ||
|
||
##### Usage: | ||
|
||
After the configuration you can use the newly set accounts system in the same way you were doing it previously. | ||
|
||
```js | ||
import RememberMe from 'meteor/tprzytula:remember-me'; | ||
|
||
RememberMe.loginWithPassword('username', 'password', (error) => { | ||
if (error) { | ||
console.error(error); | ||
return; | ||
} | ||
// success! | ||
}, true); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/** | ||
* Checks which param is the rememberMe flag | ||
* and returns it. If it's not present then | ||
* returns "true" by default. | ||
* @param {Array} params | ||
* @returns {boolean} flag | ||
*/ | ||
export const exportFlagFromParams = (params = []) => { | ||
const [ | ||
firstParam = () => {}, | ||
secondParam = true, | ||
] = params; | ||
return (typeof firstParam === 'boolean') | ||
? firstParam | ||
: secondParam; | ||
}; | ||
|
||
/** | ||
* Checks if the first provided param is the callback | ||
* function. If it's not present then returns an | ||
* empty method instead. | ||
* @param {Array} params | ||
* @returns {function} callback | ||
*/ | ||
export const exportCallbackFromParams = (params = []) => { | ||
const [firstParam] = params; | ||
return (typeof firstParam === 'function') | ||
? firstParam | ||
: () => {}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,171 @@ | ||
import { | ||
Accounts, | ||
AccountsClient | ||
} from 'meteor/accounts-base'; | ||
|
||
import { | ||
exportFlagFromParams, | ||
exportCallbackFromParams | ||
} from './helpers'; | ||
|
||
import overrideAccountsLogin from './overrideLogin'; | ||
overrideAccountsLogin(); | ||
|
||
const RememberMe = {}; | ||
const updateRememberMe = 'tprzytula:rememberMe-update'; | ||
|
||
RememberMe.loginWithPassword = (user, password, callback = () => {}, rememberMe = true) => { | ||
const flag = (typeof callback === 'boolean') | ||
? callback | ||
: rememberMe; | ||
|
||
const callbackMethod = (typeof callback === 'function') | ||
? callback | ||
: () => {}; | ||
|
||
Meteor.loginWithPassword(user, password, (error) => { | ||
if (!error) { | ||
Meteor.call(updateRememberMe, flag, (error) => { | ||
if (error && error.error === 404) { | ||
console.warn( | ||
'Dependency meteor/tprzytula:remember-me is not active!\n', | ||
'\nTo activate it make sure to run "RememberMe.activate()" on the server.' + | ||
'It is required to be able to access the functionality on the client.' | ||
) | ||
} else if (error) { | ||
console.error( | ||
'meteor/tprzytula:remember-me' + | ||
'\nCould not update remember me setting.' + | ||
'\nError:', error | ||
); | ||
|
||
/** | ||
* RememberMe | ||
* | ||
* @property {Object} remoteConnection - handler to a custom connection. | ||
* @property {string} methodName - unique name for the rememberMe method | ||
* | ||
* @class | ||
*/ | ||
class RememberMe { | ||
constructor() { | ||
this.remoteConnection = null; | ||
this.methodName = 'tprzytula:rememberMe-update'; | ||
overrideAccountsLogin(Accounts); | ||
} | ||
|
||
/** | ||
* Returns login method either from the main | ||
* connection or remote one if set. | ||
* @returns {function} loginWithPassword | ||
* @private | ||
*/ | ||
getLoginWithPasswordMethod() { | ||
return this.remoteConnection | ||
? this.remoteConnection.loginWithPassword | ||
: Meteor.loginWithPassword; | ||
} | ||
|
||
/** | ||
* Returns call method either from the main | ||
* connection or remote one if set. | ||
* @returns {function} call | ||
* @private | ||
*/ | ||
getCallMethod() { | ||
return this.remoteConnection | ||
? this.remoteConnection.call.bind(this.remoteConnection) | ||
: Meteor.call; | ||
} | ||
|
||
/** | ||
* Wrapper for the Meteor.loginWithPassword | ||
* Invokes suitable loginMethod and upon results | ||
* passes it to the user's callback and if there | ||
* were no errors then also invokes a method to | ||
* update the rememberMe flag on the server side. | ||
* @public | ||
*/ | ||
loginWithPassword(...params) { | ||
const [user, password, ...rest] = params; | ||
const flag = exportFlagFromParams(rest); | ||
const callbackMethod = exportCallbackFromParams(rest); | ||
const loginMethod = this.getLoginWithPasswordMethod(); | ||
loginMethod(user, password, (error) => { | ||
if (!error) { | ||
this.updateFlag(flag); | ||
} | ||
callbackMethod(error); | ||
}); | ||
} | ||
|
||
/** | ||
* Sends request to the server to update | ||
* the remember me setting. | ||
* @param {boolean} flag | ||
* @private | ||
*/ | ||
updateFlag(flag) { | ||
const callMethod = this.getCallMethod(); | ||
callMethod(this.methodName, flag, (error) => { | ||
if (error && error.error === 404) { | ||
console.warn( | ||
'Dependency meteor/tprzytula:remember-me is not active!\n', | ||
'\nTo activate it make sure to run "RememberMe.activate()" on the server.' + | ||
'It is required to be able to access the functionality on the client.' | ||
); | ||
} else if (error) { | ||
console.error( | ||
'meteor/tprzytula:remember-me' + | ||
'\nCould not update remember me setting.' + | ||
'\nError:', | ||
error | ||
); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Switches from using the current login system to | ||
* a new custom one. After switching each login attempt | ||
* will be performed to new accounts instance. | ||
* @param {AccountsClient} customAccounts | ||
* @returns {boolean} result | ||
* @public | ||
*/ | ||
changeAccountsSystem(customAccounts) { | ||
if (customAccounts instanceof AccountsClient && | ||
customAccounts.connection) { | ||
this.remoteConnection = customAccounts.connection; | ||
this.setLoginMethod(); | ||
overrideAccountsLogin(customAccounts); | ||
return true; | ||
} | ||
console.error('meteor/tprzytula:remember-me' + | ||
'\nProvided parameter is not a valid AccountsClient.'); | ||
return false; | ||
} | ||
|
||
/** | ||
* Since freshly created AccountsClients are not having | ||
* this method by default it's required to make sure that | ||
* the set accounts system will contain it. | ||
* @private | ||
*/ | ||
setLoginMethod() { | ||
if ('loginWithPassword' in this.remoteConnection) { | ||
// Login method is already present | ||
return; | ||
} | ||
|
||
/* eslint-disable */ | ||
/* | ||
The method is based on the original one in Accounts: | ||
https://github.com/meteor/meteor/blob/46257bad264bf089e35e0fe35494b51fe5849c7b/packages/accounts-password/password_client.js#L33 | ||
*/ | ||
this.remoteConnection.loginWithPassword = function (selector, password, callback) { | ||
if (typeof selector === 'string') { | ||
selector = selector.indexOf('@') === -1 | ||
? { username: selector } | ||
: { email: selector }; | ||
} | ||
Meteor.remoteUsers.callLoginMethod({ | ||
methodArguments: [{ | ||
user: selector, | ||
password: Accounts._hashPassword(password) | ||
}], | ||
userCallback: function (error, result) { | ||
if (error && error.error === 400 && | ||
error.reason === 'old password format') { | ||
srpUpgradePath({ | ||
upgradeError: error, | ||
userSelector: selector, | ||
plaintextPassword: password | ||
}, callback); | ||
} else if (error) { | ||
callback && callback(error); | ||
} else { | ||
callback && callback(); | ||
} | ||
} | ||
}); | ||
} | ||
callbackMethod(error); | ||
}); | ||
}; | ||
}; | ||
/* eslint-enable */ | ||
} | ||
} | ||
|
||
export default new RememberMe(); | ||
|
||
export default RememberMe; | ||
// Export handle to the class only on TEST environment | ||
export const RememberMeClass = process.env.TEST_METADATA ? RememberMe : null; |
Oops, something went wrong.