1 /*jshint node:true, indent:2, curly:false, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
2 regexp:true, undef:true, strict:true, trailing:true, white:true */
3 /*global X:true, _:true, SYS:true */
12 var passport = require('passport'),
16 Receives user authentication credentials and have passport do the authentication.
19 //passport.authenticate('local', { successReturnToOrRedirect: '/login/scope', failureRedirect: '/', failureFlash: 'Invalid username or password.' }),
20 passport.authenticate('local', { failureRedirect: '/?login=fail' }),
21 function (req, res, next) {
22 var pathName = "/app";
23 if (req && req.session && !req.session.oauth2 && req.session.passport && req.session.passport.user && req.session.passport.user.organization) {
24 if (req.body.extensions) {
25 pathName = pathName + "?extensions=" + req.body.extensions;
27 if (req.body.hash && req.body.hash.charAt(0) === "#") {
28 pathName = pathName + req.body.hash;
30 res.redirect("/" + req.session.passport.user.organization + pathName);
33 exports.scopeForm(req, res, next);
39 Renders the login form
41 exports.loginForm = function (req, res) {
44 if (req.query && req.query.login && req.query.login === 'fail') {
45 message = ["Invalid username or password."];
48 res.render('login', { message: message, databases: X.options.datasource.databases });
52 Logs out user by removing the session and sending the user to the login screen.
54 exports.logout = function (req, res) {
55 if (req.session.passport) {
56 // Make extra sure passport is empty.
57 req.session.passport = null;
61 // Kill the whole session, db, cache and all.
62 req.session.destroy(function () {});
65 if (req.path.split("/")[1]) {
66 res.clearCookie(req.path.split("/")[1] + ".sid");
74 Receives a request telling us which organization a user has selected
75 to log into. Note that we don't trust the client; we check
76 to make sure that the user actually belongs to that organization.
78 exports.scope = function (req, res, next) {
79 var userId = req.session.passport.user.id,
80 selectedOrg = req.body.org,
81 user = new SYS.User(),
84 options.success = function (response) {
89 if (response.length === 0) {
90 if (req.session && req.session.oauth2 && req.session.oauth2.redirectURI) {
91 X.log("OAuth 2.0 User %@ has no business trying to log in to organization %@.".f(userId, selectedOrg));
92 res.redirect(req.session.oauth2.redirectURI + '?error=access_denied');
96 X.log("User %@ has no business trying to log in to organization %@.".f(userId, selectedOrg));
97 res.redirect('/' + selectedOrg + '/logout');
99 } else if (response.length > 1) {
100 X.log("More than one User: %@ exists.".f(userId));
101 res.redirect('/' + selectedOrg + '/logout');
105 // We can now trust this user's request to log in to this organization.
107 // Update the session store row to add the org choice and username.
108 // Note: Updating this object magically persists the data into the SessionStore table.
110 //privs = _.map(response.get("privileges"), function (privAss) {
111 // return privAss.privilege.name;
114 //_.each(response.get('organizations'), function (orgValue, orgKey, orgList) {
115 // if (orgValue.name === selectedOrg) {
116 // userOrg = orgValue.name;
117 // userName = orgValue.username;
121 //if (!userOrg || !userName) {
122 if (!response.get("username")) {
123 // This shouldn't happen.
124 X.log("User %@ has no business trying to log in to organization %@.".f(userId, selectedOrg));
125 res.redirect('/' + selectedOrg + '/logout');
129 //req.session.passport.user.globalPrivileges = privs;
130 req.session.passport.user.organization = response.get("organization");
131 req.session.passport.user.username = response.get("username");
133 // TODO - req.oauth probably isn't enough here, but it's working 2013-03-15...
134 // If this is an OAuth 2.0 login with only 1 org.
139 // If this is an OAuth 2.0 login with more than 1 org.
140 if (req.session.returnTo) {
141 res.redirect(req.session.returnTo);
143 // Redirect to start loading the client app.
144 res.redirect('/' + selectedOrg + '/app');
148 options.error = function (model, error) {
149 X.log("userorg fetch error", error);
150 res.redirect('/' + selectedOrg + '/logout');
155 // The user id we're searching for.
158 // The user under whose authority the query is run.
159 options.username = X.options.databaseServer.user;
160 options.database = selectedOrg;
162 // Verify that the org is valid for the user.
167 Loads the form to let the user choose their organization. If there's only one
168 organization for the user we choose for them.
170 exports.scopeForm = function (req, res, next) {
171 var organizations = [],
176 organizations = _.map(req.user.get("organizations"), function (org) {
180 // Prevent unauthorized access.
185 // If this is an OAuth 2.0 login req, try and get the org from the requested scope.
186 if (req.session && req.session.oauth2) {
188 if (req.session.oauth2.req && req.session.oauth2.req.scope && req.session.oauth2.req.scope.length > 0) {
189 // Loop through the scope URIs and convert them to org names.
190 _.each(req.session.oauth2.req.scope, function (value, key, list) {
193 // Get the org from the scope URI e.g. 'dev' from: 'https://mobile.xtuple.com/auth/dev'
194 scope = url.parse(value, true);
195 org = scope.path.split("/")[1] || null;
197 // TODO - Still need more work to support userinfo calls.
198 // See node-datasource/oauth2/oauth2.js authorization.
200 // The scope 'https://mobile.xtuple.com/auth/userinfo.xxx' can be used to make userinfo
201 // REST calls and is not a valid org scope, we'll skip it here.
202 if (org && org.indexOf('userinfo') === -1) {
207 // If we only have one scope/org sent, choose it for this request.
208 if (scopes.length === 1) {
209 req.body.org = scopes[0];
210 exports.scope(req, res, next);
214 // TODO - Multiple scopes sent.
215 // Do we want to let them select an org or respond with error and scopeList?
216 // It depends on the scenario. Some support user interaction and can select an org, others
217 // do not and should get an error.
221 // TODO - No scope is sent.
222 // Do we want to let them select an org or respond with error and scopeList?
223 // It depends on the scenario. Some support user interaction and can select an org, others
224 // do not and should get an error.
228 // Below will handle OAuth "TODO - Multiple scopes sent", "TODO - No scope is sent." above for now.
230 // Choose an org automatically if there's only one for this user.
231 if (organizations.length === 1) {
232 req.body.org = organizations[0];
233 exports.scope(req, res, next);
237 // Some users may not have any orgs. They should not get this far.
238 if (organizations.length === 0) {
239 X.err("User: %@ shall not pass, they have no orgs to select.".f(req.session.passport.user.id));
240 req.flash('orgerror', 'You have not been assigned to any organizations.');
243 // We've got nothing, let the user choose their scope/org.
244 res.render('scope', { organizations: organizations.sort(), message: req.flash('orgerror') });