packages/accounts-passwordless/passwordless_client.js
import { Tracker } from 'meteor/tracker';
// Used in the various functions below to handle errors consistently
const reportError = (error, callback) => {
if (callback) {
callback(error);
} else {
throw error;
}
};
const transformSelector = selector => {
if (typeof selector !== 'string') {
return selector;
}
if (selector.includes('@')) {
return { email: selector };
}
return { username: selector };
};
const internalPasswordlessLoginWithToken = ({
selector,
token,
code,
callback,
}) => {
Accounts.callLoginMethod({
methodArguments: [
{
selector: transformSelector(selector),
token,
code,
},
],
userCallback: error => {
if (error) {
reportError(error, callback);
} else {
callback && callback();
}
},
});
};
// Attempt to log in with a token.
//
// @param selector {String|Object} One of the following:
// - {username: (username)}
// - {email: (email)}
// - a string which may be a username or email, depending on whether
// it contains "@".
// @param password {String}
// @param callback {Function(error|undefined)}
/**
* @summary Log the user in with a one time token.
* @locus Client
* @param {Object|String} selector Username, email or custom selector to identify the user.
* @param {String} token one time token generated by the server
* @param {Function} [callback] Optional callback.
* Called with no arguments on success, or with a single `Error` argument
* on failure.
* @importFromPackage meteor
*/
Meteor.passwordlessLoginWithToken = (selector, token, callback) => {
internalPasswordlessLoginWithToken({ selector, token, callback });
};
/**
* @summary Log the user in with a one time token.
* @locus Client
* @param {Object|String} selector Username, email or custom selector to identify the user.
* @param {String} token one time token generated by the server
* @param {String} code generated by the user's authenticator app
* @param {Function} [callback] Optional callback.
* Called with no arguments on success, or with a single `Error` argument
* on failure.
* @importFromPackage meteor
*/
Meteor.passwordlessLoginWithTokenAnd2faCode = (selector, token, code, callback) => {
internalPasswordlessLoginWithToken({ selector, token, code, callback });
};
/**
* @summary Request a login token.
* @locus Client
* @param {Object} options
* @param {String} options.selector The email address to get a token for or username or a mongo selector.
* @param {String} options.userData When creating a user use this data if selector produces no result.
* @param {Object} options.options For example userCreationDisabled.
* @param {Function} [callback] Optional callback. Called with no arguments on success, or with a single `Error` argument on failure.
*/
Accounts.requestLoginTokenForUser = (
{ selector, userData, options },
callback
) => {
if (!selector) {
return reportError(new Meteor.Error(400, 'Must pass selector'), callback);
}
Accounts.connection.call(
'requestLoginTokenForUser',
{ selector: transformSelector(selector), userData, options },
callback
);
};
const checkToken = ({ selector, token }) => {
if (!token) {
return;
}
const userId = Tracker.nonreactive(Meteor.userId);
if (!userId) {
Meteor.passwordlessLoginWithToken(selector, token, () => {
// Make it look clean by removing the authToken from the URL
if (window.history) {
const url = window.location.href.split('?')[0];
window.history.pushState(null, null, url);
}
});
}
};
/**
* Parse querystring for token argument, if found use it to auto-login
*/
Accounts.autoLoginWithToken = function() {
Meteor.startup(function() {
const params = new URL(window.location.href).searchParams;
if (params.get('loginToken')) {
const rawSelector = params.get('selector');
checkToken({
selector: rawSelector.startsWith('{')
? JSON.parse(rawSelector)
: rawSelector,
token: params.get('loginToken'),
});
}
});
};
// Run check for login token on page load
Meteor.startup(() => Accounts.autoLoginWithToken());