├── .versions ├── LICENSE.txt ├── README.md ├── accounts-passwordless-ui.html ├── accounts-passwordless-ui.js ├── accounts-passwordless.js ├── example ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── packages │ ├── platforms │ ├── release │ └── versions └── example.html └── package.js /.versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.0 2 | acemtp:accounts-passwordless@0.2.3 3 | base64@1.0.3 4 | binary-heap@1.0.3 5 | blaze@2.1.2 6 | blaze-tools@1.0.3 7 | callback-hook@1.0.3 8 | check@1.0.5 9 | ddp@1.1.0 10 | deps@1.0.7 11 | ejson@1.0.6 12 | email@1.0.6 13 | geojson-utils@1.0.3 14 | html-tools@1.0.4 15 | htmljs@1.0.4 16 | id-map@1.0.3 17 | jperl:match-ex@1.0.0 18 | jquery@1.11.3_2 19 | json@1.0.3 20 | localstorage@1.0.3 21 | logging@1.0.7 22 | meteor@1.1.6 23 | minifiers@1.1.5 24 | minimongo@1.0.8 25 | mongo@1.1.0 26 | observe-sequence@1.0.6 27 | ordered-dict@1.0.3 28 | random@1.0.3 29 | reactive-dict@1.1.0 30 | reactive-var@1.0.5 31 | retry@1.0.3 32 | service-configuration@1.0.4 33 | session@1.1.0 34 | spacebars-compiler@1.0.6 35 | templating@1.1.1 36 | tracker@1.0.7 37 | underscore@1.0.3 38 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Chris Mather 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # meteor-accounts-passwordless 2 | 3 | Passwords are broken. Passwordless is an open source Meteor package for token-based one-time password (OTPW) authentication, which is faster to deploy, better for your users, and more secure. 4 | 5 | ## Install 6 | 7 | ``` 8 | meteor add acemtp:accounts-passwordless 9 | ``` 10 | 11 | ## Usage 12 | 13 | 14 | You have 2 ways to use it, the highlevel that use the default ui or the low level to plug on your own application. 15 | 16 | ### Default UI 17 | 18 | This is the easiest way to use the package. Add this line in a template and voila: 19 | 20 | {{> loginPasswordless}} 21 | 22 | This is how it's done on the [live demo](http://passwordless.meteor.com). The source code of this example is on [Github](https://github.com/efounders/meteor-accounts-passwordless/tree/master/example). 23 | 24 | ### Low Level API 25 | 26 | If the default layout doesn't fit your needs, you can call the low level api. You can copy how it's made on the [default ui source file](https://github.com/efounders/meteor-accounts-passwordless/blob/master/accounts-passwordless-ui.js). 27 | 28 | Basically, there're 3 methods you have to call on the client: 29 | 30 | #### Meteor.sendVerificationCode(selector, callback) 31 | 32 | Call this one will send the verification code to the user. 33 | 34 | The `selector` can be the email of the user or his username. If you pass the username, the accounts must already exists to find the associate email and send the email. 35 | 36 | The callback has 2 parameters, `error` and `result`. 37 | 38 | #### Meteor.loginWithPasswordless(options, callback) 39 | 40 | options is an object that must contain the `code` entered by the user after he read the email. It can also contains `selector` that was the selector used at the `Meteor.sendVerificationCode` step. 41 | 42 | That's all you need to log in a user with passwordless. 43 | 44 | #### Meteor.setUsername(username, callback) 45 | 46 | You don't have to call this function. It's just an utility function to set the username of the logged user, in case you don't want to display the user email. 47 | 48 | #### Workflow 49 | 50 | Here is the minimal workflow you have to implement: 51 | 52 | - ask the user his email or username 53 | - call `Meteor.sendVerificationCode` with the value given by the user 54 | - ask the user his verification code sent by email 55 | - call `Meteor.loginWithPasswordless` with the verification code 56 | - the user is logged 57 | 58 | Some optional extra steps: 59 | 60 | - (optional) ask the user his username and call `Meteor.setUsername` with the value given by the user 61 | - (optional) call `Meteor.logout()` to logout the user 62 | 63 | #### Set a link in the email 64 | 65 | To set a link inside the email you can modifier the `emailTemplates` object to use custom texte and so add a link. Below you get an example: 66 | 67 | ```javascript 68 | Meteor.startup(function () { 69 | 70 | Accounts.passwordless.emailTemplates.sendVerificationCode = { 71 | subject: function (code) { 72 | return "Your verification code is " + code + " for " + Accounts.passwordless.emailTemplates.siteName; 73 | }, 74 | text: function (user, code, selector, options) { 75 | 76 | var greeting = (user && user.username) ? ("Hello " + user.username + ",") : "Hello,"; 77 | 78 | var loginURL = Meteor.absoluteUrl().replace(/^https?:\/\//, '').replace(/\/$/, '') + '/login/'; 79 | loginURL += encodeURIComponent(selector) + '/' + code; 80 | 81 | if (options && options.length == 2) { 82 | // options come from client and must be checked 83 | check(options[0], String); 84 | check(options[1], String); 85 | loginURL += '/' + options[0] + '/' + options[1]; 86 | } 87 | 88 | return greeting + "\n" 89 | + "\n" 90 | + "Your verification code is " + code + ".\n" 91 | + "You can login directly by clicking this link\n" 92 | + "\n" 93 | + "Thanks.\n"; 94 | } 95 | }; 96 | }); 97 | ``` 98 | 99 | After you have to set a root to get email/username and code, to login the user. Look example code below for an example with iron:router 100 | 101 | ``` 102 | Router.route('/login/:selector/:code', function () { 103 | // 104 | var options = { 105 | code: this.params.code, 106 | selector: decodeURIComponent(this.params.selector) 107 | }; 108 | 109 | Meteor.loginWithPasswordless(options, function (error, result) { 110 | if (error) { 111 | console.error(error); 112 | } else { 113 | // redirect user to your main page. 114 | Router.go('dashboard'); 115 | } 116 | }); 117 | }); 118 | ``` 119 | 120 | ### Test the example locally on your computer 121 | 122 | - git clone https://github.com/efounders/meteor-accounts-passwordless.git 123 | - cd meteor-accounts-passwordless/example 124 | - meteor 125 | - then open a browser to [http://localhost:3000](http://localhost:3000) 126 | - since the email is not configured by default on your computer, the email (verification code) will be displayed on the server console. 127 | -------------------------------------------------------------------------------- /accounts-passwordless-ui.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 29 | 30 | 41 | 42 | 49 | -------------------------------------------------------------------------------- /accounts-passwordless-ui.js: -------------------------------------------------------------------------------- 1 | // set to false to skip the form asking the username 2 | var loginPasswordlessAskUsername = true; 3 | 4 | Session.setDefault('loginPasswordlessMessage', ''); 5 | Session.setDefault('loginPasswordlessState', 'loginPasswordlessLogin'); 6 | 7 | Tracker.autorun(function () { 8 | var user = Meteor.user(); 9 | if(user) { 10 | if(user.username || !loginPasswordlessAskUsername) 11 | Session.set('loginPasswordlessState', 'loginPasswordlessLogout'); 12 | else 13 | Session.set('loginPasswordlessState', 'loginPasswordlessAskUsername'); 14 | } 15 | }); 16 | 17 | /// 18 | 19 | Template.registerHelper('loginPasswordlessMessage', function () { 20 | return Session.get('loginPasswordlessMessage'); 21 | }); 22 | 23 | Template.loginPasswordless.helpers({ 24 | loginPasswordlessState: function () { 25 | return Session.get('loginPasswordlessState'); 26 | } 27 | }); 28 | 29 | Template.loginPasswordlessLogin.events({ 30 | 'submit #loginPasswordlessLogin': function (event) { 31 | var selector = event.target.selector.value; 32 | console.log('login', selector); 33 | Meteor.sendVerificationCode(selector, function (err, res) { 34 | console.log('sendVerificationCode answered', arguments); 35 | if(err) 36 | Session.set('loginPasswordlessMessage', err.error); 37 | else { 38 | Session.set('loginPasswordlessMessage', ''); 39 | Session.set('loginPasswordlessState', 'loginPasswordlessVerify'); 40 | } 41 | }); 42 | return false; 43 | } 44 | }); 45 | 46 | /// 47 | 48 | Template.loginPasswordlessVerify.events({ 49 | 'submit #loginPasswordlessVerify': function (event) { 50 | var code = event.target.code.value; 51 | console.log('verify', code); 52 | Meteor.loginWithPasswordless({ code: code }, function (err, res) { 53 | console.log('loginWithPasswordless answered', arguments); 54 | if(err) 55 | Session.set('loginPasswordlessMessage', err.error); 56 | else { 57 | Session.set('loginPasswordlessMessage', ''); 58 | if(loginPasswordlessAskUsername && !Meteor.user().username) 59 | Session.set('loginPasswordlessState', 'loginPasswordlessAskUsername'); 60 | else 61 | Session.set('loginPasswordlessState', 'loginPasswordlessLogout'); 62 | } 63 | }); 64 | return false; 65 | }, 66 | 'click #loginPasswordlessVerifyBack': function (event) { 67 | Session.set('loginPasswordlessMessage', ''); 68 | Session.set('loginPasswordlessState', 'loginPasswordlessLogin'); 69 | }, 70 | }); 71 | 72 | /// 73 | 74 | Template.loginPasswordlessAskUsername.events({ 75 | 'submit #loginPasswordlessAskUsername': function (event) { 76 | var username = event.target.username.value; 77 | console.log('username', username); 78 | Meteor.setUsername(username, function (err, res) { 79 | console.log('setUsername answered', arguments); 80 | if(err) 81 | Session.set('loginPasswordlessMessage', err.error); 82 | else { 83 | Session.set('loginPasswordlessMessage', ''); 84 | Session.set('loginPasswordlessState', 'loginPasswordlessLogout'); 85 | } 86 | }); 87 | return false; 88 | } 89 | }); 90 | 91 | 92 | /// 93 | 94 | Template.loginPasswordlessLogout.helpers({ 95 | loginPasswordlessUserInfo: function () { 96 | var user = Meteor.user(); 97 | if(!user) return ''; 98 | if(user.username) return user.username; 99 | else if(user.emails && user.emails.length > 0) return user.emails[0].address; 100 | else return user._id; 101 | } 102 | }); 103 | 104 | Template.loginPasswordlessLogout.events({ 105 | 'submit #loginPasswordlessLogout': function (event) { 106 | console.log('logout'); 107 | Meteor.logout(function (err, res) { 108 | console.log('logout answered', arguments); 109 | if(err) 110 | Session.set('loginPasswordlessMessage', err.error); 111 | else { 112 | Session.set('loginPasswordlessMessage', ''); 113 | Session.set('loginPasswordlessState', 'loginPasswordlessLogin'); 114 | } 115 | }); 116 | return false; 117 | } 118 | }); 119 | -------------------------------------------------------------------------------- /accounts-passwordless.js: -------------------------------------------------------------------------------- 1 | Accounts.passwordless = {}; 2 | 3 | if (Meteor.isClient) { 4 | /** 5 | * Request a verification code. 6 | * @param selector The email or username of the user 7 | * @param [callback] 8 | */ 9 | Meteor.sendVerificationCode = function (selector, options, callback) { 10 | if (!callback && typeof options === 'function') 11 | callback = options; 12 | 13 | // Save the selector in a Session so even if the client reloads, the selector is stored 14 | Session.set('accounts-passwordless.selector', selector); 15 | Meteor.call('accounts-passwordless.sendVerificationCode', selector, options, callback); 16 | }; 17 | 18 | /** 19 | * Login with the verification code. 20 | * @param options code The verification code. selector The username or email (optional) 21 | * @param [callback] 22 | */ 23 | Meteor.loginWithPasswordless = function (options, callback) { 24 | console.log('lwpl', options); 25 | Accounts.callLoginMethod({ 26 | methodArguments: [{ 27 | selector: Session.get('accounts-passwordless.selector') || options.selector, 28 | code: options.code 29 | }], 30 | userCallback: callback 31 | }); 32 | }; 33 | 34 | /** 35 | * Set username. The user must be logged 36 | * @param username The name of the user 37 | * @param [callback] 38 | */ 39 | Meteor.setUsername = function (username, callback) { 40 | Meteor.call('accounts-passwordless.setUsername', username, callback); 41 | }; 42 | 43 | } 44 | 45 | 46 | ////////////////////////////////////////////////////////////////////////////////////////// 47 | ////////////////////////////////////////////////////////////////////////////////////////// 48 | ////////////////////////////////////////////////////////////////////////////////////////// 49 | ////////////////////////////////////////////////////////////////////////////////////////// 50 | 51 | 52 | if(Meteor.isServer) { 53 | 54 | Accounts.passwordless.emailTemplates = { 55 | from: "Meteor Accounts ", 56 | siteName: Meteor.absoluteUrl().replace(/^https?:\/\//, '').replace(/\/$/, ''), 57 | 58 | sendVerificationCode: { 59 | subject: function (code) { 60 | return "Your verification code is " + code + " for " + Accounts.passwordless.emailTemplates.siteName; 61 | }, 62 | text: function (user, code) { 63 | var greeting = (user && user.username) ? 64 | ("Hello " + user.username + ",") : "Hello,"; 65 | return greeting + "\n" 66 | + "\n" 67 | + "Your verification code is " + code + ".\n" 68 | + "\n" 69 | + "Thanks.\n"; 70 | } 71 | } 72 | }; 73 | 74 | Meteor.methods({ 75 | 'accounts-passwordless.sendVerificationCode': function (selector, options) { 76 | check(selector, String); 77 | check(options, Match.Optional(Match.Any)); 78 | Accounts.passwordless.sendVerificationCode(selector, options); 79 | }, 80 | 'accounts-passwordless.setUsername': function (username) { 81 | check(username, String); 82 | if(!this.userId) throw new Meteor.Error('You must be logged to change your username'); 83 | if(username.length < 3) throw new Meteor.Error('Your username must have at least 3 characters'); 84 | var existingUser = Meteor.users.findOne({ username: username }); 85 | if(existingUser) throw new Meteor.Error('This username already exists'); 86 | Meteor.users.update(this.userId, { $set: { username: username } }); 87 | } 88 | }); 89 | 90 | // Handler to login with passwordless 91 | Accounts.registerLoginHandler('passwordless', function (options) { 92 | if (options.code === undefined) return undefined; // don't handle 93 | 94 | check(options, { 95 | selector: String, 96 | code: String 97 | }); 98 | 99 | if(!options.selector) throw new Meteor.Error('No selector setuped'); 100 | 101 | return Accounts.passwordless.verifyCode(options.selector, options.code); 102 | }); 103 | 104 | var codes = new Meteor.Collection('meteor_accounts_passwordless'); 105 | 106 | /** 107 | * Send a 4 digit verification code by email 108 | * @param selector The email or username of the user 109 | */ 110 | Accounts.passwordless.sendVerificationCode = function (selector, options) { 111 | var email; 112 | var user; 113 | if (selector.indexOf('@') === -1) { 114 | user = Meteor.users.findOne({ username: selector }); 115 | if(!user) throw new Meteor.Error('Username \''+selector+'\' doesn\'t exists, enter your email address to create your account instead of your username.'); 116 | if(!user.emails || user.emails.length < 1) throw new Meteor.Error('No email attached to user ' + selector); 117 | email = user.emails[0].address; 118 | } else { 119 | user = Meteor.users.findOne({ 'emails.address': selector }); 120 | // If the user doesn't exists, we'll create it when the user will verify his email 121 | email = selector; 122 | } 123 | 124 | var code = Math.floor(Random.fraction() * 10000) + ''; 125 | // force pin to 4 digits 126 | code = ('0000' + code).slice(-4); 127 | 128 | // Generate a new code 129 | codes.upsert({ email: email }, { $set: { code: code }}); 130 | 131 | Email.send({ 132 | to: email, 133 | from: Accounts.passwordless.emailTemplates.from, 134 | subject: Accounts.passwordless.emailTemplates.sendVerificationCode.subject(code), 135 | text: Accounts.passwordless.emailTemplates.sendVerificationCode.text(user, code, selector, options) 136 | }); 137 | }; 138 | 139 | // from accounts-password code 140 | var createUser = function (options) { 141 | // Unknown keys allowed, because a onCreateUserHook can take arbitrary 142 | // options. 143 | check(options, Match.ObjectIncluding({ 144 | username: Match.Optional(String), 145 | email: Match.Optional(String), 146 | })); 147 | 148 | var username = options.username; 149 | var email = options.email; 150 | if (!username && !email) 151 | throw new Meteor.Error(400, "Need to set a username or email"); 152 | 153 | var user = {services: {}}; 154 | if (options.password) { 155 | var hashed = hashPassword(options.password); 156 | user.services.password = { bcrypt: hashed }; 157 | } 158 | 159 | if (username) 160 | user.username = username; 161 | if (email) 162 | user.emails = [{address: email, verified: false}]; 163 | 164 | return Accounts.insertUserDoc(options, user); 165 | }; 166 | 167 | 168 | /** 169 | * Verify if the code is valid 170 | * @param selector The email or username of the user 171 | * @param code The code the user entered 172 | */ 173 | Accounts.passwordless.verifyCode = function (selector, code) { 174 | var user; 175 | var email; 176 | if (selector.indexOf('@') === -1) { 177 | user = Meteor.users.findOne({ username: selector }); 178 | if(!user) throw new Meteor.Error('Username '+selector+' doesn\'t exists, create an accounts first or login with the email'); 179 | if(!user.emails || user.emails.length < 1) throw new Meteor.Error('No email attached to user '+selector); 180 | email = user.emails[0].address; 181 | } else { 182 | user = Meteor.users.findOne({ 'emails.address': selector }); 183 | email = selector; 184 | } 185 | 186 | var validCode = codes.findOne({ email: email}); 187 | if (!validCode) 188 | throw new Meteor.Error('Unknown email'); 189 | 190 | var now = new Date().getTime() / 1000; 191 | var timeToWait; 192 | 193 | if (validCode.lastTry) { 194 | timeToWait = validCode.lastTry.getTime()/1000 + Math.pow(validCode.nbTry, 2); 195 | 196 | if (timeToWait > now) 197 | throw new Meteor.Error('You must wait ' + Math.ceil(timeToWait - now) + ' seconds'); 198 | } 199 | 200 | if (validCode.code !== code) { 201 | codes.update({email: email}, { $set: {lastTry: new Date()}, $inc: {nbTry: 1 }}); 202 | throw new Meteor.Error('Invalid verification code'); 203 | } 204 | // Clear the verification code after a succesful login. 205 | codes.remove({ email: email }); 206 | 207 | var uid; 208 | if(user) { 209 | uid = user._id; 210 | } else { 211 | uid = createUser({ email: email }); 212 | user = Meteor.users.findOne(uid); 213 | console.log('created user', uid, user); 214 | } 215 | 216 | if(user) { 217 | // Set the email as verified since he validated the code with this email 218 | var ve = _.find(user.emails, function (e) { return e.address === email; }); 219 | if(ve && !ve.verified) { 220 | // By including the address in the query, we can use 'emails.$' in the 221 | // modifier to get a reference to the specific object in the emails 222 | // array. See 223 | // http://www.mongodb.org/display/DOCS/Updating/#Updating-The%24positionaloperator) 224 | // http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull 225 | Meteor.users.update({ _id: uid, 'emails.address': email }, { $set: { 'emails.$.verified': true } }); 226 | } 227 | } 228 | return { userId: uid }; 229 | }; 230 | 231 | 232 | } 233 | -------------------------------------------------------------------------------- /example/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | -------------------------------------------------------------------------------- /example/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /example/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1pfvxd41s5tbo1p5b4aq 8 | -------------------------------------------------------------------------------- /example/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-platform 8 | autopublish 9 | insecure 10 | accounts-ui 11 | twbs:bootstrap 12 | acemtp:accounts-passwordless 13 | -------------------------------------------------------------------------------- /example/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /example/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.0.5 2 | -------------------------------------------------------------------------------- /example/.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.0 2 | accounts-ui@1.1.5 3 | accounts-ui-unstyled@1.1.7 4 | acemtp:accounts-passwordless@0.1.0 5 | autopublish@1.0.3 6 | autoupdate@1.2.0 7 | base64@1.0.3 8 | binary-heap@1.0.3 9 | blaze@2.1.0 10 | blaze-tools@1.0.3 11 | boilerplate-generator@1.0.3 12 | callback-hook@1.0.3 13 | check@1.0.5 14 | ddp@1.1.0 15 | deps@1.0.7 16 | ejson@1.0.6 17 | email@1.0.6 18 | fastclick@1.0.3 19 | geojson-utils@1.0.3 20 | html-tools@1.0.4 21 | htmljs@1.0.4 22 | http@1.1.0 23 | id-map@1.0.3 24 | insecure@1.0.3 25 | jperl:match-ex@1.0.0 26 | jquery@1.11.3_2 27 | json@1.0.3 28 | launch-screen@1.0.2 29 | less@1.0.13 30 | livedata@1.0.13 31 | localstorage@1.0.3 32 | logging@1.0.7 33 | meteor@1.1.5 34 | meteor-platform@1.2.2 35 | minifiers@1.1.4 36 | minimongo@1.0.7 37 | mobile-status-bar@1.0.3 38 | mongo@1.1.0 39 | observe-sequence@1.0.5 40 | ordered-dict@1.0.3 41 | random@1.0.3 42 | reactive-dict@1.1.0 43 | reactive-var@1.0.5 44 | reload@1.1.3 45 | retry@1.0.3 46 | routepolicy@1.0.5 47 | service-configuration@1.0.4 48 | session@1.1.0 49 | spacebars@1.0.6 50 | spacebars-compiler@1.0.5 51 | templating@1.1.0 52 | tracker@1.0.6 53 | twbs:bootstrap@3.3.4 54 | ui@1.0.6 55 | underscore@1.0.3 56 | url@1.0.4 57 | webapp@1.2.0 58 | webapp-hashing@1.0.3 59 | -------------------------------------------------------------------------------- /example/example.html: -------------------------------------------------------------------------------- 1 | 2 | example 3 | 4 | 5 | 6 |
7 |

Accounts Passwordless Demo

8 | {{> loginPasswordless}} 9 |
10 | 11 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'acemtp:accounts-passwordless', 3 | version: '0.2.3', 4 | summary: 'Token-based one-time password (OTPW) authentication (nopassword, passwordless)', 5 | git: 'https://github.com/efounders/meteor-accounts-passwordless', 6 | documentation: 'README.md' 7 | }); 8 | 9 | Package.onUse(function(api) { 10 | api.versionsFrom('METEOR@0.9.2'); 11 | 12 | api.use(['tracker', 'underscore', 'templating', 'session'], 'client'); 13 | api.use('email', 'server'); 14 | api.use(['accounts-base', 'check'], ['client', 'server']); 15 | 16 | // Export Accounts (etc) to packages using this one. 17 | api.imply('accounts-base', ['client', 'server']); 18 | 19 | api.use(['random', 'jperl:match-ex@1.0.0'], 'server'); 20 | 21 | api.addFiles(['accounts-passwordless-ui.html', 'accounts-passwordless-ui.js'], 'client'); 22 | api.addFiles('accounts-passwordless.js'); 23 | }); 24 | --------------------------------------------------------------------------------