UNPKG

25.7 kBMarkdownView Raw
1# Activator
2
3## Overview
4activator is the **simple** way to handle user activation and password reset for your nodejs apps!
5
6Example:
7
8 var express = require('express'), app = express(), activator = require('activator');
9
10 activator.init({user:userModel,transport:smtpURL,from:"activator@github.com",templates:mailTemplatesDir});
11
12 app.user(app.router);
13
14 // activate a user
15 app.post("/user",activator.createActivate);
16 app.put("/user/:user/active",activator.completeActivate);
17
18 // reset a password
19 app.post("/passwordreset",activator.createPasswordReset);
20 app.put("/passwordreset/:user",activator.completePasswordReset);
21
22## Breaking Changes
23
24#### Express versions
25Activator version >= 1.0.0 works **only** with express >=4.0.0
26
27Activator version <1.0.0 works **only** with express <4.0.0
28
29#### Algorithms
30Activator version >= 2.0.0 works **only** with [JSON Web Tokens](https://tools.ietf.org/html/rfc7519) and ignores **completely** the user database fields for password reset code and password reset time.
31
32Activator version < 2.0.0 work **only** with custom fields in the database to store the password reset code, password reset time, and activation code.
33
34**The user model used for activator < 2.0.0 is incompatible with the one for activator >= 2.0.0**.
35
36The signature prior to 2.0.0 was:
37
38````javascript
39user = {
40 find(id,callback),
41 save(id,model,callback)
42}
43````
44
45The signature beginning with 2.0.0 is:
46
47````javascript
48user = {
49 find(id,callback),
50 activate(id,callback),
51 setPassword(id,password,callback)
52}
53````
54
55
56
57## Purpose
58Most interaction between users and your Web-driven service take place directly between the user and the server: log in, send a message, join a group, post an update, close a deal, etc. The user logs in by entering a username and password, and succeeds (or doesn't); the user enters a message and clicks "post"; etc.
59
60There are a few key interactions - actually, mainly just two - that take place using side channels and with delays:
61
62* New user creation & activation
63* Password reset
64
65In both of these cases, the user does something directly on the Web (or via your mobile app or API), then something happens "on the side", usually via email or SMS: a confirmation email is sent; a password reset token is texted; etc.
66
67This process is quite burdensome to build into your app, since it breaks the usual "request-response" paradigm.
68
69*Activator* is here to make this process easier.
70
71## Process
72### Activator Services
73Activator provides express middleware that to perform user activation - create and complete - and password reset - create and complete. It handles one-time link creation, link expiry, validation and all the other parts necessary to make user activation and password reset turnkey.
74
75*activator* also does not tell you what the email you send out should look like; you just provide a template, and activator fills it in.
76
77Here are activator's steps in detail.
78
79### User Activation
80For user creation, the steps are normally as follows:
81
821. User creates a new account on your Web site / app
832. System creates an "activation email" that contains a one-time link and sends it to the registered email
843. User clicks on the link, thus validating the email address
85
86Most sites call steps 2-3 "user activation". Activator calls step 2 "create an activation" and step 3 "complete an activation".
87
88When you use activator, the steps are as follows:
89
901. User creates a new account on your Web site / app
912. You include `activator.createActivate()` as part of the creation middleware
923. *activator* takes the user email address from the new user account, a template from the templates directory set on initialization, and the URL from initialization, creates a one-time activation key, composes an email and sends it.
934. The user receives the email and clicks on the link
945. You included `activator.completeActivate()` as the express middleware handler for the path in the URL
956. *activator* checks the one-time activation key and other information against the user account, marks the account as activated
96
97
98### Password Reset
99For password reset, the steps are normally as follows:
100
1011. User clicks "forgot password" on your Web site / app
1022. System creates a "password reset email" that contains a one-time link and sends it to the registered email for the account
1033. User clicks on the link, allowing them the opportunity to set a new password
104
105Activator calls step 2 "create a password reset" and step 3 "complete a password reset"
106
107When you use activator, the steps are as follows:
108
1091. User selects "reset password" on your Web site / app
1102. You include `activator.createPasswordReset()` as the express middleware handler for the path
1113. *activator* takes the user email address from the user account, a template from the templates directory set on initialization, and the URL from initialization, creates a one-time password reset key, composes an email and sends it.
1124. The user receives the email and clicks on the link
1135. You included `activator.completePasswordReset()` as the express middleware handler for the path in the URL
1146. *activator* checks the one-time password reset key and other information against the user account, and then allows the user to reset the password
115
116
117
118### How To Use It
119To use *activator*, you select the routes you wish to use - activator does not impose any special routes - and use activator as middleware. Of course, you will need to tell activator how to do several things, like:
120
121* How to find a user, so it can check for the user
122* How to mark a user as activated, once they have sent the correct verified code
123* How to change a user's password, once they have sent the correct verified code within time and a new password
124* Where to find the templates to use for activation and password reset emails
125* What URL the user should be using to activate or reset a password. The URL is included in the email, since the user normally clicks on a link.
126
127All of these are described in greater detail below.
128
129
130## Installation
131Installation is simple, just install the npm module:
132
133 npm install activator
134
135
136## Usage
137First *initialize* your activator instance, then use its methods to activate users and reset passwords
138
139### Initialization
140In order for activator to work, it needs to be able to read your user instances and save to them. It also needs to be able to compose and send emails.
141
142 activator = require('activator');
143 activator.init(config);
144
145The `config` object passed to `activator.init()` **must** contain the following keys:
146
147* `user`: object that allows activator to find a user object, indicate activation, set a new password. See below.
148* `emailProperty`: the property of the returned user object that is the email of the recipient. Used in `user.find()`. Defaults to "email". Use dot notation to specify a property not at the root, e.g. "profiles.local.email"
149* `transport`: string or pre-configured nodemailer transport that describes how we will send email. See below.
150* `templates`: string describing the full path to the mail templates. See below.
151* `from`: string representing the sender for all messages
152* `signkey`: A string used to sign all of the JWT with HS256. If it is not present, activator has no way of confirming key signing between processes or from one startup of the process to the next.
153
154Optionally, config can also contain:
155
156* `id`: the property that contains the ID in a user when it is found using `find`. Use dot notation to specify a property not at the root, e.g. "profiles.local.remoteid". See below for `user.save()`
157* `attachments`: object with attachments to include in messages. See below for detailed attachment formats.
158* `styliner`: boolean that turns on styliner for template compilation
159
160
161
162##### user
163The user object needs to have three methods, with the following signatures:
164
165 user.find(login,callback);
166 user.activate(id,callback);
167 user.setPassword(id,password,callback);
168
169###### find a user
170
171 user.find(login,callback);
172
173Where:
174
175* `login`: string with which the user logs in. activator doesn't care if it is an email address, a user ID, or the colour of their parrot. `user.find()` should be able to find a user based on it.
176* `callback`: the callback function that `user.find()` should call when complete. Has the signature `callback(err,data)`. If there is an error, `data` should be `null` or `undefined`; if there is no error but no users found, both `err` *and* `data` **must** be `null` (not `undefined`). If an object is found, then `data` **must** be a single JavaScript object. The `data` object should have:
177 - a property containing the user id. By default, it is named `id`, but you can override it with `config.id`.
178 - a property containing the email address. By default, it is named `email`, but you can override it with `config.emailProperty`.
179 - a property named `activation_code` if the user has a stored activation code.
180 - a property named `password_reset_code` if the user has a stored password reset code.
181 - a property named `password_reset_time` if the user has a stored password reset time.
182
183
184###### activate a user
185
186 user.activate(id,callback);
187
188Where:
189
190* `id`: ID of the user to activate.
191* `callback`: the callback function that `user.activate()` should call when complete. Has the signature `callback(err)`. If the save is successful, `err` **must** be `null` (not `undefined`).
192
193activator does not care how you mark the user as activated or not. It doesn't even care of you never check activation (but that is a *really* bad idea, right?). All it cares is that you give it a way to indicate successful activation.
194
195###### set a password
196
197 user.setPassword(id,password,callback);
198
199Where:
200
201* `id`: ID of the user to change password
202* `password`: new password for the user
203* `callback`: the callback function that `user.activate()` should call when complete. Has the signature `callback(err)`. If the save is successful, `err` **must** be `null` (not `undefined`).
204
205
206
207##### User ID
208
209What ID does it use when activating or setting the password?
210
211* If you passed an `id` parameter to `activator.init(config)`, then it is that property of the user. For example, `activator.init({id:'uid'})` means that when activator does `user.find('me@email.com')` and the returned object contains `{uid:12345}`, then activator will activate as `user.activate(12345)`
212* If you did not pass an `id` parameter, then it is the exact same search term as used in `user.find()`. else it is the search term used as `login` in `user.find(login)`. For example, if activator does `user.find('12bc5')` then it will also do `user.activate('12bc5')`.
213
214
215
216##### transport
217There are 2 ways activator can send email: SMTP (default) or a passed-in transport.
218
219###### SMTP
220If you are using SMTP - which is the default - all you need to pass in is a string describing how activator should connect with your mail server. It is structured as follows:
221
222 protocol://user:pass@hostname:port/domain?secureConnection=true
223
224* `protocol`: normally "smtp", can be "smtps"
225* `user`: the user with which to login to the SMTP server, if authentication is required.
226* `pass`: the password with which to login to the SMTP server, if authentication is required.
227* `hostname`: the hostname of the server, e.g. "smtp.gmail.com".
228* `port`: the port to use.
229* `domain`: the domain from which the mail is sent, when the mail server is first connected to.
230* `secureConnection`: the use of SSL can be guided by the query parameter "secureConnection=true".
231
232###### Other
233If you prefer a different service - or you want to override the SMTP configuration - then instead of passing a URL string to transport, you can pass in a preconfigured nodemailer transport object. Since activator uses nodemailer under the covers, the transport is a pass-through.
234
235And, yes, you can even use the nodemailer SMTP transport instead of a URL string, if you prefer. Once activator receives a configured transport object, rather than a string, it doesn't care what it is as long as it works.
236
237How would you do it? Well, SMTP would normally look like this:
238
239 activator.init({transport:"smtp://user:pass@mysmtp.com/me.com"});
240
241Or similar.
242
243To use a pre-configured transport, you need to:
244
2451. Include the transport module
2462. Configure the transport
2473. Initialize activator with the transport
248
249Here is an SMTP example:
250
251 var smtpTransport = require('nodemailer-smtp-transport'), mailer = require('nodemailer');
252 var transport = mailer.createTransport(smtpTransport(options));
253 activator.init({transport:transport});
254
255Of course, because the 'nodemailer-smtp-transport' is the default in nodemailer, the above example is **identical** to just passing in a URL string, but you can work whichever way works for you.
256
257Here is an Amazon Simple Email Service (SES) example:
258
259 var sesTransport = require('nodemailer-ses-transport'), mailer = require('nodemailer');
260 var transport = mailer.createTransport(sesTransport(options));
261 activator.init({transport:transport});
262
263In all cases, it is up to *you* to set the `options` to create the transport.
264
265And if all you know (or want to know) is SMTP, then just use the default SMTP connection with a URL string.
266
267For details aboute nodemailer's transports, see the nodemailer transports at http://www.nodemailer.com/#available-transports
268
269##### templates
270The directory where you keep text files that serve as mail templates. See below under the section templates.
271
272##### attachments
273The initialization object property `attachments` is an object with 0, 1 or 2 keys:
274
275* `activate`: the attachment to add to activation messages
276* `passwordreset`: the attachment to add to password reset messages
277
278The value for each of these attachments is an object matching the `attachments` object format from https://github.com/andris9/Nodemailer#attachments
279
280##### styliner
281The boolean value for the initialization object property styliner specifies whether the [styliner](http://styliner.slaks.net/) libary should be used to compile your html templates. This libary provides inlining of css styles from `<style>` tags for better Gmail support.
282
283### Responses and Your Handlers
284All of the middleware available in activator can function in one of two modes:
285
2861. Send responses - this is usually used by Ajax, e.g. `res.send(200,"success")` or `res.send(401,"invalidcode")`
2872. Pass responses - pass the responses on to your middleware, where you can do what you wish
288
289
290Here are two examples, one of each:
291
292````JavaScript
293app.post("/users",createUser,activator.createActivate); // will send the success/error directly
294app.post("/users",createUser,activator.createActivateNext,handler); // will call next() when done
295````
296
297When the middleware is done, if it ends in "Next", it will store the results in a `req.activator` object and then call `next()`.
298
299````JavaScript
300req.activator = {
301 code: 500, // or 401 or 400 or 201 or 200 or ....
302 message: "uninitialized" // of null/undefined, or "invalidcode" or ....
303}
304````
305
306
307### Activation
308Activation is the two-step process wherein a user first *creates* their account and *then* confirms (or activates) it by clicking on a link in an email or entering a short code via SMS/iMessage/etc.
309
310activator provides the route handlers to create the activation code on the account and send the email, and then confirm the entered code to mark the user activated.
311
312activator does **not** create the user; it leaves that up to you, since everyone likes to do it just a little differently.
313
314
315#### Create an activation
316Activation is simple, just add the route handler *after* you have created the user:
317
318````JavaScript
319app.post("/users",createUser,activator.createActivate); // direct send() mode
320app.post("/users",createUser,activator.createActivateNext,handler); // save results in req.activator and call next() mode
321````
322
323`activator.createActivate` needs access to several pieces of data in order to do its job:
324
325* `id`: It needs the ID of the user, so that it can call `user.save(id,data)`
326* `response.body`: Since `createUser` (in the above example) or anything you have done to create a user might actually want to send data back, `createActivate()` needs to be able to know what the body you want to send is, when it is successful and calls `res.send(201,data);`
327
328`createActivate()` will look for these properties on `req.activator`.
329
330````JavaScript
331req.activator = {
332 id: "12345tg", // the user ID to pass to createActivate()
333 body: "A message" // the body to send back along with the successful 201
334}
335````
336
337If `createActivate()` or `createActivateNext()` cannot find `req.activator.id` or `req.user.id`, it will incur a `500` error.
338
339
340#### Complete an activation
341Once the user actually clicks on the link, you need to complete the activation:
342
343````JavaScript
344app.put("/users/:user/activation",activator.completeActivate); // direct res.send() mode
345app.put("/users/:user/activation",activator.completeActivateNext,handler); // save results and call next() mode
346````
347
348activator will return a `200` if successful, a `400` if there is an error, along with error information, and a `404` if it cannot find that user.
349
350activator assumes the following:
351
3521. The express parameter `user` (i.e. `/users/:user/whatever/foo`) contains the user identifier to pass to `user.find()` as the first parameter. It will retrieve it using `req.param('user')`
3532. The `req` contains the JWT for the activation. It will look in three places. First, it will check `req.headers.Authorization` for the JWT from the message in `Bearer` format, following the RFC. If it does not find it in the `Authorization` header, it will look in the query `req.query.authorization`, and then in the body `req.body.authorization`.
354
355If it is successful activating, it will return `200`, a `400` if there is an error (including invalid activation code), and a `404` if the user cannot be found.
356
357### Password Reset
358Password reset is a two-step process in which the user requests a password reset link, normally delivered by email, and then uses that link to set a new password. Essentially, the user requests a time-limited one-time code that is delivered to the user and allows them to set a new password.
359
360#### Create a password reset
361Creating a password reset is simple, just add the route handler:
362
363````JavaScript
364app.post("/passwordreset",activator.createPasswordReset); // direct res.send() mode
365app.post("/passwordreset",activator.createPasswordResetNext,handler); // save data and call next() mode
366````
367
368When done, activator will return a `201` code and a message whose text content is the URL to be used to reset the password.
369
370Activator assumes that the login/email/ID to search for will be in `req.param("user")`.
371
372#### Complete a password reset
373Once the user actually clicks on the link, you need to complete the password reset:
374
375````JavaScript
376app.put("/users/:user/passwordreset",activator.completePasswordReset); // direct res.send() mode
377app.put("/users/:user/passwordreset",activator.completePasswordResetNext,handler); // save response and call next() mode
378````
379
380activator will return a `200` if successful, a `400` if there is an error, along with error information, and a `404` if it cannot find that user.
381
382activator assumes the following:
383
3841. The express parameter `user` (i.e. `/users/:user/whatever/foo`) contains the user identifier to pass to `user.find()` as the first parameter. It will retrieve it using `req.param('user')`
3852. The `req` contains the JWT for the activation. It will look in three places. First, it will check `req.headers.Authorization` for the JWT from the message in `Bearer` format, following the RFC. If it does not find it in the `Authorization` header, it will look in the query `req.query.authorization`, and then in the body `req.body.authorization`.
386
387If it is successful resetting the password, it will return `200`, a `400` if there is an error (including invalid code), and a `404` if the user cannot be found.
388
389
390### Templates
391In order to send an email (yes, we are thinking about SMS for the future), activator needs to have templates. The templates are simple text files that contain the text or HTML to send.
392
393The templates should be in the directory passed to `activator.init()` as the option `templates`. It **must** be an absolute directory path (how else is activator going to know, relative to what??). Each template file should be named according to its function: "activate" or "passwordreset". You can, optionally, add ".txt" to the end of the filename, if it makes your life easier.
394
395Each template file must have 3 or more lines. The first line is the `Subject` of the email; the second is ignored (I like to use '-----', but whatever works for you), the third and all other lines are the content of the email.
396
397Remember, activator is a *server-side* product, so it really has no clue if the page the user should go to is https://myserver.com/funny/page/activate/fooooo.html or something a little more sane like https://myserver.com/activate.html
398
399How does activator know what to put in the email? **It doesn't; you do!**. You put the URL in the template files for passwordreset and activate.
400
401What you can do is have activator embed the necessary information into the templates before sending the email. Each template file follows a simplified [EJS](http://embeddedjs.com) style (very similar to PHP). All you need to do is embed the following anywhere (and as many times as you want) inside the template:
402
403 <%= abc %>
404
405and every such instance will be replaced by the value of `abc`. So if `abc = "John"`, then
406
407 This is an email for <%= abc %>,
408 hi there <%= abc %>.
409
410Will be turned into
411
412 This is an email for John,
413 hi there John.
414
415So what variables are available inside the templates?
416
417* `code`: the activation or password reset JSON Web Token
418* `authorization`: the activation or password reset JSON Web Token
419* `email`: the email of the recipient user
420* `id`: the internal user ID of the user
421* `request`: the `request` object that was passed to the route handler, from which you can extract lots of headers, for example the protocol at `req.protocol` or the hostname from `req.headers.host`.
422
423So if your password reset page is on the same host and protocol as the request that came in but at "/reset/my/password", and you want to include the code in the URL as part of a query but also add it to the page, you could do:
424
425
426 Hello,
427
428 You have asked to reset your password for MySite. To reset your password, please click on the following link:
429
430 <%= request.protocol %><%= request.headers.host %>/reset/my/password?code=<%= code %>&user=<%= id %>
431
432 Or just copy and paste that link and enter your code as <%= code %>.
433
434 Thanks!
435 From: the MySite team
436
437
438
439#### HTML and text templates
440Template files can be either text or HTML. If activator finds html, it will send html email; if activator finds text, it will send text email; if it finds both, it will send both in an email.
441
442How does it know which? Simple: **filename extension**.
443
444* `activate.html` - use this as an HTML template for activation
445* `passwordreset.html` - use this as an HTML template for password reset
446* `activate.txt` - use this as a text template for activation
447* `passwordreset.txt` - use this as a text template for password reset
448* `activate` - use this as a text template for activation
449* `passwordreset` - use this as a text template for password reset
450
451Notice that there are two options for text templates: no filename extension (e.g. `activate`) and text extension (e.g. `activate.txt`). How does it know which one to use when both are there? Simple:
452
4531. Use the filename without an extension. If it does not exist:
4542. Use the filename with the `.txt` extension.
455
456The content format of both kinds of templates (html and text) is the same as described above and have all of the same variables.
457
458
459#### Localized templates
460
461Activator supports localized templates. You can have one template for the locale `en_GB`, a separate one for `fr` and a third for `he_IL`. Just create the files with the correct name as an extension: filename type (e.g. `activate`), followed by `_` followed by the locale string (e.g. `en_GB` or `fr`) following by the optional filetype extension (nothing or `.txt` or `.html`).
462
463Here are some examples:
464
465* `activate_en_GB.txt` - text template for locale `en_GB`
466* `activate_en_GB` - text template for locale `en_GB`
467* `activate_en_GB.html` - html template for locale `en_GB`
468* `activate_fr.html` - html template for locale `fr`, will be used when the language is `fr` or `fr_`*anything* that is not matched
469* `activate` - fallback for all unmatched locales
470
471The search pattern is as follows.
472
4731. Look for an exact match of the locale, e.g. for `en_GB`, look for `activate_en_GB`
4742. Look for a language match, e.g. for `en_GB`, look for `activate_en`
4753. Look for a default file, e.g. for `en_GB`, look for `activate`
476
477How does it know which language to use? Simple, just set it on `req.lang`. You might have retrieved that from your user preferences, or from your application's default, or perhaps from the http header `Accept-Language`. Either way, you should set it in earlier middleware:
478
479````JavaScript
480 app.use(function(req,res,next){
481 req.lang = myLang; // Set your lang here
482 });
483 app.use(app.router);
484 app.post('/users',activator.createActivate); // etc.
485````
486
487
488## Example
489An example - just a simplified and stripped down version of the tests - is available in `./example.js`. It can be run via `node ./example.js`
490
491## Testing
492To run the tests, from the root directory, run `npm test`.
493
494## License
495Released under the MIT License.
496Copyright Avi Deitcher https://github.com/deitch
497
\No newline at end of file