diff --git a/packages/accounts-passwordless/package.js b/packages/accounts-passwordless/package.js index 99bc15296a..a79ee9c634 100644 --- a/packages/accounts-passwordless/package.js +++ b/packages/accounts-passwordless/package.js @@ -23,4 +23,5 @@ Package.onUse(api => { api.addFiles('email_templates.js', 'server'); api.addFiles('passwordless_server.js', 'server'); api.addFiles('passwordless_client.js', 'client'); + api.addFiles('server_utils.js', 'server'); }); diff --git a/packages/accounts-passwordless/passwordless_server.js b/packages/accounts-passwordless/passwordless_server.js index e69de29bb2..a603bf715b 100644 --- a/packages/accounts-passwordless/passwordless_server.js +++ b/packages/accounts-passwordless/passwordless_server.js @@ -0,0 +1,54 @@ +import { Accounts } from 'meteor/accounts-base'; +import { + handleError, + tokenValidator, + userQueryValidator, +} from './server_utils'; + +Accounts._checkToken = ({ user, token }) => { + const result = { + userId: user._id, + }; + + const userStoredToken = user.services.passwordless.token; + const { createdAt, sequence } = userStoredToken; + + if(new Date(createdAt.getTime() + (Accounts._options.loginTokenExpirationHours*60*60*1000)) >= new Date()){ + result.error = handleError("Expired token", false); + } + if(sequence !== token){ + result.error = handleError("Sequence not found", false); + } + + return result; +}; +const checkToken = Accounts._checkToken; + +// Handler to login with an ott. +Accounts.registerLoginHandler('passwordless', options => { + if (!options.token) return undefined; // don't handle + + check(options, { + user: userQueryValidator, + token: tokenValidator(), + }); + + const user = Accounts._findUserByQuery(options.user, { + fields: { + services: 1, + }, + }); + if (!user) { + handleError('User not found'); + } + + if ( + !user.services || + !user.services.passwordless || + !user.services.passwordless.token + ) { + handleError('User has no token set'); + } + + return checkToken({ ...options, user }); +}); diff --git a/packages/accounts-passwordless/server_utils.js b/packages/accounts-passwordless/server_utils.js new file mode 100644 index 0000000000..212fe46ec0 --- /dev/null +++ b/packages/accounts-passwordless/server_utils.js @@ -0,0 +1,35 @@ +import {Accounts} from "meteor/accounts-base"; + +export const handleError = (msg, throwError = true) => { + const error = new Meteor.Error( + 403, + Accounts._options.ambiguousErrorMessages + ? "Something went wrong. Please check your credentials." + : msg + ); + if (throwError) { + throw error; + } + return error; +}; +export const NonEmptyString = Match.Where(x => { + check(x, String); + return x.length > 0; +}); + +export const userQueryValidator = Match.Where(user => { + check(user, { + id: Match.Optional(NonEmptyString), + username: Match.Optional(NonEmptyString), + email: Match.Optional(NonEmptyString), + }); + if (Object.keys(user).length !== 1) + throw new Match.Error('User property must have exactly one field'); + return true; +}); +export const tokenValidator = () => { + const tokenLength = Accounts._options.tokenLength || 6; + return Match.Where( + str => Match.test(str, String) && str.length <= tokenLength + ); +};