Coverage

97%
121
118
3

userauth.js

97%
121
118
3
LineHitsSource
1/*!
2 * userauth - lib/userauth.js
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * MIT Licensed
5 */
6
71"use strict";
8
9/**
10 * Module dependencies.
11 */
12
131var urlparse = require('url').parse;
14
15/**
16 * Send redirect response.
17 *
18 * @param {Response} res, http.Response instance
19 * @param {String} url, redirect URL
20 * @param {Number|String} status, response status code, default is `302`
21 * @api public
22 */
231var redirect = function (res, url, status) {
2457 status = status === 301 ? 301 : 302;
2557 res.setHeader('Location', url);
26
2757 var body = '';
2857 var accept = (res.req.headers && res.req.headers.accept) || '';
2957 if (accept.indexOf('json') >= 0) {
304 status = 401;
314 res.setHeader('Content-Type', 'application/json');
324 body = JSON.stringify({ error: '401 Unauthorized' });
33 }
3457 res.statusCode = status;
3557 res.end(body);
36};
37
381function formatReferer(req, pathname) {
3921 var query = req.query;
4021 if (!query) {
4121 query = urlparse(req.originalUrl || req.url, true).query;
42 }
4321 var referer = query.redirect || req.headers.referer || '/';
4421 referer = typeof referer === 'string' ? referer : '/';
4521 if (referer[0] !== '/') {
46 // ignore http://xxx/abc
474 referer = '/';
4817 } else if (referer.indexOf(pathname) >= 0) {
492 referer = '/';
50 }
5121 return referer;
52}
53
541function login(options) {
552 return function (req, res, next) {
5610 req.session._loginReferer = formatReferer(req, options.loginPath);
5710 var currentURL = 'http://' + req.headers.host + options.loginCallbackPath;
5810 var loginURL = options.loginURLForamter(currentURL);
5910 redirect(res, loginURL);
60 };
61}
62
631function loginCallback(options) {
642 return function (req, res, next) {
6526 var referer = req.session._loginReferer || '/';
6626 var user = req.session[options.userField];
6726 if (user) {
68 // already login
6910 return redirect(res, referer);
70 }
7116 options.getUser(req, function (err, user) {
7216 if (err) {
73 // 5. get user error, next(err)
741 return next(err);
75 }
76
7715 if (!user) {
785 return redirect(res, referer);
79 }
80
8110 options.loginCallback(req, user, function (err, loginUser, redirectURL) {
8210 if (err) {
831 return next(err);
84 }
85
869 req.session[options.userField] = loginUser;
879 if (redirectURL) {
881 referer = redirectURL;
89 }
909 redirect(res, referer);
91 });
92 });
93 };
94}
95
961function logout(options) {
972 return function (req, res, next) {
9811 var referer = formatReferer(req, options.logoutPath);
9911 var user = req.session[options.userField];
10011 if (!user) {
1017 return redirect(res, referer);
102 }
103
1044 options.logoutCallback(req, res, user, function (err, redirectURL) {
1054 if (err) {
1061 return next(err);
107 }
108
1093 req.session[options.userField] = null;
1103 if (redirectURL) {
1111 referer = redirectURL;
112 }
1133 redirect(res, referer);
114 });
115 };
116}
117
118/**
119 * User auth middleware.
120 *
121 * @param {Regex|Function(pathname, req)} match, detect which url need to check user auth.
122 * @param {Object} [options]
123 * - {Function(url)} loginURLForamter, format the login url.
124 * - {String} [loginPath], default is '/login'.
125 * - {String} [loginCallbackPath], default is `options.loginPath + '/callback'`.
126 * - {String} [logoutPath], default is '/logout'.
127 * - {String} [userField], logined user field name on `req.session`, default is 'user', `req.session.user`.
128 * - {Function(req, callback)} getUser, get user function, must get user info with `req`.
129 * - {Function(req, user, callback)} [loginCallback], you can handle user login logic here.
130 * - {Function(err, user, redirectURL)} callback
131 * - {Function(req)} [loginCheck], return true meaning logined. default is `true`.
132 * - {Function(req, res, user, callback)} [logoutCallback], you can handle user logout logic here.
133 * - {Function(err, redirectURL)} callback
134 * @return {Function(req, res, next)} userauth middleware
135 * @public
136 */
1371module.exports = function userauth(match, options) {
1382 options = options || {};
1392 options.userField = options.userField || 'user';
1402 options.loginPath = options.loginPath || '/login';
1412 options.loginCallbackPath = options.loginCallbackPath || options.loginPath + '/callback';
1422 options.logoutPath = options.logoutPath || '/logout';
1432 options.loginURLForamter = options.loginURLForamter;
1442 options.getUser = options.getUser;
145
1462 var defaultRedirectHandler = function (req, res, nextHandler) {
1474 nextHandler();
148 };
1492 options.redirectHandler = options.redirectHandler || defaultRedirectHandler;
150
1512 var needLogin = match;
152
1532 if (typeof match === 'string') {
1540 match = new RegExp('^' + match);
155 }
156
1572 if (match instanceof RegExp) {
1582 needLogin = function (pathname, req) {
15925 return match.test(pathname);
160 };
1610 } else if (typeof match !== 'function') {
1620 needLogin = function () {};
163 }
164
1652 var defaultLoginCallback = function (req, user, callback) {
1661 return callback(null, user, null);
167 };
1682 var defaultLogoutCallback = function (req, res, user, callback) {
1691 return callback(null, null);
170 };
171
1722 options.loginCallback = options.loginCallback || defaultLoginCallback;
1732 options.logoutCallback = options.logoutCallback || defaultLogoutCallback;
174 // options.loginCheck = options.loginCheck;
175
1762 var loginHandler = login(options);
1772 var loginCallbackHandler = loginCallback(options);
1782 var logoutHandler = logout(options);
179
180 /**
181 * login flow:
182 *
183 * 1. unauth user, redirect to `$loginPath?redirect=$currentURL`
184 * 2. user visit `$loginPath`, redirect to `options.loginURLForamter()` return login url.
185 * 3. user visit $loginCallbackPath, handler login callback logic.
186 * 4. If user login callback check success, will set `req.session[userField]`,
187 * and redirect to `$currentURL`.
188 * 5. If login check callback error, next(err).
189 * 6. user visit `$logoutPath`, set `req.session[userField] = null`, and redirect back.
190 */
191
1922 return function authMiddleware(req, res, next) {
19372 if (!res.req) {
19472 res.req = req;
195 }
196
19772 var url = req.originalUrl || req.url;
19872 var urlinfo = urlparse(url);
199
200 // 2. GET $loginPath
20172 if (urlinfo.pathname === options.loginPath) {
20210 return loginHandler(req, res, next);
203 }
204
205 // 3. GET $loginCallbackPath
20662 if (urlinfo.pathname === options.loginCallbackPath) {
20726 return loginCallbackHandler(req, res, next);
208 }
209
210 // 6. GET $logoutPath
21136 if (urlinfo.pathname === options.logoutPath) {
21211 return logoutHandler(req, res, next);
213 }
214
21525 if (!needLogin(urlinfo.pathname, req)) {
2165 return next();
217 }
218
21920 if (req.session[options.userField] && (!options.loginCheck || options.loginCheck(req))) {
220 // 4. user logined, next() handler
2211 return next();
222 }
223
224 // check user logined or not
225 // If user auth token vaild, just getUser() directly
22619 options.getUser(req, function (err, user) {
22719 if (err) {
2281 return next(err);
229 }
230
23118 if (!user) {
232 // 1. redirect to $loginPath
23313 var nextHandler = function () {
23412 var redirectURL = url;
23512 try {
23612 redirectURL = encodeURIComponent(redirectURL);
237 } catch (e) {
238 // URIError: URI malformed
239 // use source url
240 }
24112 redirect(res, options.loginPath + '?redirect=' + redirectURL);
242 };
24313 return options.redirectHandler(req, res, nextHandler);
244 }
245
2465 options.loginCallback(req, user, function (err, loginUser, redirectURL) {
2475 if (err) {
2481 return next(err);
249 }
250
2514 req.session[options.userField] = loginUser;
2524 if (redirectURL) {
2531 return redirect(res, redirectURL);
254 }
2553 next();
256 });
257 });
258
259 };
260};