NOTICKET: Refactor with sessions. Now holds auth on server
Signed-off-by: KIRAN KASHALKAR <kiran.kashalkar@riftio.com>
diff --git a/skyquake/framework/core/api_utils/constants.js b/skyquake/framework/core/api_utils/constants.js
index 0aac7d2..7f54797 100644
--- a/skyquake/framework/core/api_utils/constants.js
+++ b/skyquake/framework/core/api_utils/constants.js
@@ -73,6 +73,7 @@
constants.SOCKET_POOL_LENGTH = 20;
constants.SERVER_PORT = process.env.SERVER_PORT || 8000;
constants.SECURE_SERVER_PORT = process.env.SECURE_SERVER_PORT || 8443;
+constants.REJECT_UNAUTHORIZED = false;
constants.BASE_PACKAGE_UPLOAD_DESTINATION = 'upload/packages/';
constants.PACKAGE_MANAGER_SERVER_PORT = 4567;
diff --git a/skyquake/framework/core/api_utils/sockets.js b/skyquake/framework/core/api_utils/sockets.js
index 5e0b25b..0cd4918 100644
--- a/skyquake/framework/core/api_utils/sockets.js
+++ b/skyquake/framework/core/api_utils/sockets.js
@@ -33,6 +33,7 @@
var sockjs = require('sockjs');
var websocket_multiplex = require('websocket-multiplex');
var utils = require('./utils.js');
+var configurationAPI = require('../modules/api/configuration.js');
var Subscriptions = function() {
@@ -278,7 +279,7 @@
self.isClosed = false;
var requestHeaders = {};
_.extend(requestHeaders, {
- 'Authorization': req.get('Authorization')
+ Cookie: req.get('Cookie')
});
var pollServer = function() {
diff --git a/skyquake/framework/core/api_utils/utils.js b/skyquake/framework/core/api_utils/utils.js
index 5b17279..68775f8 100644
--- a/skyquake/framework/core/api_utils/utils.js
+++ b/skyquake/framework/core/api_utils/utils.js
@@ -95,7 +95,7 @@
var checkAuthorizationHeader = function(req) {
return new Promise(function(resolve, reject) {
- if (req.get('Authorization') == null) {
+ if (req.session && req.session.authorization == null) {
reject();
} else {
resolve();
@@ -200,7 +200,7 @@
uri: uri,
method: 'GET',
headers: _.extend({}, CONSTANTS.HTTP_HEADERS.accept[type], {
- 'Authorization': req.get('Authorization'),
+ 'Authorization': req.session && req.session.authorization,
forever: CONSTANTS.FOREVER_ON,
rejectUnauthorized: false,
})
diff --git a/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js b/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js
index b0223b2..34d30b3 100644
--- a/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js
+++ b/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js
@@ -36,7 +36,7 @@
uri: utils.confdPort(api_server) + '/api/schema/nsd-catalog/nsd',
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
@@ -46,7 +46,7 @@
uri: utils.confdPort(api_server) + '/api/schema/vnfd-catalog/vnfd',
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
diff --git a/skyquake/framework/core/modules/api/projectManagementAPI.js b/skyquake/framework/core/modules/api/projectManagementAPI.js
index d73eb21..8eace6b 100644
--- a/skyquake/framework/core/modules/api/projectManagementAPI.js
+++ b/skyquake/framework/core/modules/api/projectManagementAPI.js
@@ -36,7 +36,7 @@
uri: utils.confdPort(api_server) + '/api/operational/project',
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
@@ -75,7 +75,7 @@
uri: utils.confdPort(api_server) + '/api/config/project',
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
json: data,
@@ -115,7 +115,7 @@
uri: utils.confdPort(api_server) + '/api/config/project',
method: 'PUT',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
json: data,
@@ -157,7 +157,7 @@
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
rp({
url: url,
diff --git a/skyquake/framework/core/modules/api/restconf.js b/skyquake/framework/core/modules/api/restconf.js
index 5ba0eb5..03f2721 100644
--- a/skyquake/framework/core/modules/api/restconf.js
+++ b/skyquake/framework/core/modules/api/restconf.js
@@ -45,7 +45,7 @@
url: uri + url + '?deep',
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
diff --git a/skyquake/framework/core/modules/api/sessions.js b/skyquake/framework/core/modules/api/sessions.js
new file mode 100644
index 0000000..c8a0223
--- /dev/null
+++ b/skyquake/framework/core/modules/api/sessions.js
@@ -0,0 +1,205 @@
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * sessions api module. Provides API functions for sessions
+ * @module framework/core/modules/api/sessions
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var Promise = require('bluebird');
+var constants = require('../../api_utils/constants');
+var utils = require('../../api_utils/utils');
+var request = utils.request;
+var rp = require('request-promise');
+var sessionsAPI = {};
+var _ = require('lodash');
+var base64 = require('base-64');
+var APIVersion = '/v2';
+
+function logAndReject(mesg, reject) {
+ res.errorMessage = {
+ error: mesg
+ }
+ console.log(mesg);
+ reject(res);
+}
+
+function logAndRedirectToLogin(mesg, res, req) {
+ console.log(mesg);
+ res.render('login.html?api_server=' + req.query['api_server']);
+ res.end();
+}
+
+sessionsAPI.create = function(req, res) {
+ var api_server = req.query["api_server"];
+ var uri = utils.confdPort(api_server);
+ var login_url = uri + APIVersion + '/api/login';
+ var project_url = uri + APIVersion + '/api/operational/project';
+ var authorization_header_string = 'Basic ' + base64.encode(req.body['username'] + ':' + req.body['password']);
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ rp({
+ url: login_url,
+ method: 'POST',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': authorization_header_string
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: constants.REJECT_UNAUTHORIZED,
+ resolveWithFullResponse: true
+ }),
+ rp({
+ url: project_url,
+ method: 'GET',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, {
+ 'Authorization': authorization_header_string
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: constants.REJECT_UNAUTHORIZED,
+ resolveWithFullResponse: true
+ })
+
+ ]).then(function(results) {
+ // results[0].statusCode => 200/201
+ // results[1].body.collection['rw-project:project'] => List of projects OR 204 with no content
+ if (results[0].statusCode != constants.HTTP_RESPONSE_CODES.SUCCESS.OK) {
+ var errorMsg = 'Invalid credentials provided!';
+ logAndRedirectToLogin(errorMsg, res, req);
+ return;
+ }
+
+ var username = req.body['username'];
+ var project_list_for_user = [];
+
+ if (results[1].statusCode == constants.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT) {
+ console.log('No projects added or user ', username ,' not privileged to view projects.');
+ } else {
+ // go through projects and get list of projects that this user belongs to.
+ // pick first one as default project?
+
+ var projects = JSON.parse(results[1].body).collection['rw-project:project'];
+ projects && projects.map(function(project) {
+ project['project-config'] &&
+ project['project-config']['user'] &&
+ project['project-config']['user'].map(function(user) {
+ if (user['user-name'] == username) {
+ project_list_for_user.push(project.name);
+ }
+ });
+ });
+
+ req.session.projectId = (project_list_for_user.length > 0) && project_list_for_user[0];
+ }
+
+ req.session.authorization = authorization_header_string;
+ req.session.loggedIn = true;
+
+ var successMsg = 'User =>' + username + ' successfully logged in.';
+ successMsg += req.session.projectId ? 'Project =>' + req.session.projectId + ' set as default.' : '';
+
+ console.log(successMsg);
+
+ var response = {
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.CREATED,
+ data: JSON.stringify({
+ status: successMsg
+ })
+ };
+ resolve(response);
+
+ }).catch(function(error) {
+ // Something went wrong - Redirect to /login
+ var errorMsg = 'Error logging in or getting list of projects. Error: ' + error;
+ console.log(errorMsg);
+ logAndRedirectToLogin(errorMsg, res, req);
+ });
+ })
+};
+
+sessionsAPI.addProjectToSession = function(req, res) {
+ return new Promise(function(resolve, reject) {
+ if (req.session && req.session.loggedIn == true) {
+ req.session.projectId = req.params.projectId;
+ var successMsg = 'Added project' + projectId + ' to session' + req.sessionID;
+ console.log(successMsg);
+
+ return resolve ({
+ statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK,
+ data: JSON.stringify({
+ status: successMsg
+ })
+ });
+ }
+
+ var errorMsg = 'Session does not exist or not logged in';
+ logAndReject(errorMsg, reject);
+ });
+}
+
+sessionsAPI.delete = function(req, res) {
+ var reqRef = req;
+ var res = res;
+ var api_server = req.query["api_server"];
+ var uri = utils.confdPort(api_server);
+ var url = uri + '/api/logout';
+ return new Promise(function(resolve, reject) {
+ Promise.all([
+ rp({
+ url: url,
+ method: 'POST',
+ headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
+ 'Authorization': req.session.authorization
+ }),
+ forever: constants.FOREVER_ON,
+ rejectUnauthorized: constants.REJECT_UNAUTHORIZED
+ }),
+ new Promise(function(success, failure) {
+ req.session.destroy(function(err) {
+ if (err) {
+ var errorMsg = 'Error deleting session. Error: ' + err;
+ console.log(errorMsg);
+ success({
+ status: 'error',
+ message: errorMsg
+ });
+ }
+
+ var successMsg = 'Success deleting session';
+ console.log(successMsg);
+
+ success({
+ status: 'success',
+ message: successMsg
+ });
+ });
+ })
+ ]).then(function(result) {
+ // assume the session was deleted!
+ var message = 'Session was deleted.'
+ logAndRedirectToLogin(message, res, req);
+
+ }).catch(function(error) {
+ var message = 'Error deleting session or logging out. Error:' + error;
+ logAndRedirectToLogin(message, res, req);
+ });
+ });
+}
+
+
+module.exports = sessionsAPI;
diff --git a/skyquake/framework/core/modules/api/userManagementAPI.js b/skyquake/framework/core/modules/api/userManagementAPI.js
index 3972122..e88a5dc 100644
--- a/skyquake/framework/core/modules/api/userManagementAPI.js
+++ b/skyquake/framework/core/modules/api/userManagementAPI.js
@@ -36,7 +36,7 @@
uri: utils.confdPort(api_server) + '/api/operational/user-config/users',
method: 'GET',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
rejectUnauthorized: false,
@@ -75,7 +75,7 @@
uri: utils.confdPort(api_server) + '/api/config/user-config',
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
json: data,
@@ -115,7 +115,7 @@
uri: utils.confdPort(api_server) + '/api/operations/change-password',
method: 'POST',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
json: {
@@ -136,7 +136,7 @@
uri: utils.confdPort(api_server) + '/api/config/user-config',
method: 'PUT',
headers: _.extend({}, constants.HTTP_HEADERS.accept.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
}),
forever: constants.FOREVER_ON,
json: data,
@@ -179,7 +179,7 @@
_.extend(requestHeaders,
constants.HTTP_HEADERS.accept.data,
constants.HTTP_HEADERS.content_type.data, {
- 'Authorization': req.get('Authorization')
+ 'Authorization': req.session && req.session.authorization
});
rp({
url: url,
diff --git a/skyquake/framework/core/modules/routes/configuration.js b/skyquake/framework/core/modules/routes/configuration.js
index b789ff0..37d87fe 100644
--- a/skyquake/framework/core/modules/routes/configuration.js
+++ b/skyquake/framework/core/modules/routes/configuration.js
@@ -74,7 +74,7 @@
uri: uri,
method: 'GET',
headers: _.extend({}, {
- 'Authorization': req.get('Authorization'),
+ 'Authorization': req.session && req.session.authorization,
forever: CONSTANTS.FOREVER_ON,
rejectUnauthorized: false,
})
diff --git a/skyquake/framework/core/modules/routes/navigation.js b/skyquake/framework/core/modules/routes/navigation.js
index 82c7ec5..9b8fbf7 100644
--- a/skyquake/framework/core/modules/routes/navigation.js
+++ b/skyquake/framework/core/modules/routes/navigation.js
@@ -37,8 +37,20 @@
extended: true
}));
-Router.get('/', cors(), function(req, res, next) {
- res.redirect('/launchpad/?api_server=' + req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server + '&upload_server=' + req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname));
+Router.get('/login.html', cors(), function(req, res) {
+ res.render('login.html');
+ res.end();
+});
+
+Router.get('/', cors(), function(req, res) {
+ var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server);
+ if (req.session.loggedIn) {
+ console.log('Logged in. Redirect to launchpad')
+ res.redirect('/launchpad/?api_server=' + api_server + '&upload_server=' + req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname));
+ } else {
+ console.log('Redirect to login.html');
+ res.redirect('login.html?api_server=' + api_server);
+ }
});
Router.get('/nav', cors(), function(req, res) {
diff --git a/skyquake/framework/core/modules/routes/sessions.js b/skyquake/framework/core/modules/routes/sessions.js
new file mode 100644
index 0000000..bd2179b
--- /dev/null
+++ b/skyquake/framework/core/modules/routes/sessions.js
@@ -0,0 +1,62 @@
+
+/*
+ *
+ * Copyright 2016 RIFT.IO Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * Node sessions routes module.
+ * Provides a RESTful API to manage sessions.
+ * @module framework/core/modules/routes/sessions
+ * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+ */
+
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var sessionsAPI = require('../api/sessions');
+var Router = require('express').Router();
+var utils = require('../../api_utils/utils');
+var CONSTANTS = require('../../api_utils/constants.js');
+var request = require('request');
+var _ = require('lodash');
+
+Router.use(bodyParser.json());
+Router.use(cors());
+Router.use(bodyParser.urlencoded({
+ extended: true
+}));
+
+Router.post('/session', cors(), function(req, res) {
+ sessionsAPI.create(req, res).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ });
+});
+
+// For project switcher UI
+Router.put('/session/:projectId', cors(), function(req, res) {
+ sessionsAPI.addProjectToSession(req, res).then(function(data) {
+ utils.sendSuccessResponse(data, res);
+ }, function(error) {
+ utils.sendErrorResponse(error, res);
+ });
+});
+
+Router.delete('/session', cors(), function(req, res) {
+ sessionsAPI.delete(req, res);
+});
+
+
+module.exports = Router;
diff --git a/skyquake/framework/core/views/login.html b/skyquake/framework/core/views/login.html
new file mode 100644
index 0000000..eb5e714
--- /dev/null
+++ b/skyquake/framework/core/views/login.html
@@ -0,0 +1,118 @@
+<html>
+<head>
+<style>
+ html, body {
+ height:100%;
+ width:100%;
+ background: #f1f1f1;
+ }
+
+ #loginForm {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ text-align: center;
+ align-items: center;
+ }
+
+ #loginForm .logo {
+ background: url('/img/svg/osm-logo_color_rgb.svg') no-repeat center;
+ background-size: contain;
+ width: 154px;
+ height: 102px;
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 150px;
+ margin-bottom: 20px;
+ }
+
+ #loginForm input {
+ width: 550px;
+ height: 65px;
+ min-width: auto;
+ margin-bottom: 40px;
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, .39), 0 -1px 1px #fff, 0 1px 0 #fff;
+ font-size: 20px;
+ padding-left: 25px;
+ }
+
+ #loginForm #submit {
+ display: inline-block;
+ -webkit-box-shadow: 4px 4px 1px 0 #d9d9d9;
+ -moz-box-shadow: 4px 4px 1px 0 #d9d9d9;
+ box-shadow: 4px 4px 1px 0 #d9d9d9;
+ background-color: #333;
+ color: #fff;
+ text-decoration: none;
+ }
+
+ #loginForm .title {
+ margin-bottom: 40px;
+ font-size: 1.625rem;
+ font-weight: 400;
+ text-decoration: none;
+ text-transform: uppercase;
+ font-family: roboto-thin, Helvetica, Arial, sans-serif;
+ }
+
+</style>
+<title>Login Page</title>
+<script src='/jquery'></script>
+<script>
+
+function getSearchParams(url) {
+ var a = document.createElement('a');
+ a.href = url;
+ var params = {};
+ var items = a.search.replace('?', '').split('&');
+ for (var i = 0; i < items.length; i++) {
+ if (items[i].length > 0) {
+ var key_value = items[i].split('=');
+ params[key_value[0]] = key_value[1];
+ }
+ }
+ return params;
+}
+
+$(document).ready(function() {
+ var username;
+ var pass;
+ var api_server = getSearchParams(window.location).api_server;
+ $('#submit').click(function() {
+ username=$('#username').val();
+ pass=$('#password').val();
+ /*
+ * Perform some validation here.
+ */
+ $.ajax({
+ url: '/session?api_server=' + api_server,
+ type: 'POST',
+ data: {
+ username: username,
+ password: pass
+ },
+ success: function(data) {
+ window.location.href='/?api_server=' + api_server;
+ }
+ });
+ });
+
+ $('#loginForm').on('keyup', function(e) {
+ if (e.keyCode == 13) {
+ $('#submit').click();
+ }
+ });
+});
+
+</script>
+</head>
+<body>
+ <form id='loginForm' autocomplete='on'>
+ <div class='logo'> </div>
+ <h1 class='title'>Launchpad Login</h1>
+ <input type='text' size='40' placeholder='Username' id='username'><br />
+ <input type='password' size='40' placeholder='Password' id='password'><br />
+ <input type='button' value='Submit' id='submit'>
+ </form>
+</body>
+</html>
\ No newline at end of file
diff --git a/skyquake/framework/utils/utils.js b/skyquake/framework/utils/utils.js
index e8e9ad1..be3278d 100644
--- a/skyquake/framework/utils/utils.js
+++ b/skyquake/framework/utils/utils.js
@@ -129,8 +129,9 @@
}
Utils.addAuthorizationStub = function(xhr) {
- var Auth = window.sessionStorage.getItem("auth");
- xhr.setRequestHeader('Authorization', 'Basic ' + Auth);
+ // NO-OP now that we are dealing with it on the server
+ // var Auth = window.sessionStorage.getItem("auth");
+ // xhr.setRequestHeader('Authorization', 'Basic ' + Auth);
};
Utils.getByteDataWithUnitPrefix = function(number, precision) {
@@ -208,10 +209,22 @@
window.sessionStorage.removeItem("auth");
AuthActions.notAuthenticated();
window.sessionStorage.setItem("locationRefHash", window.location.hash);
+ $.ajax({
+ url: '//' + window.location.hostname + ':' + window.location.port + '/session?api_server=' + API_SERVER,
+ type: 'DELETE',
+ success: function(data) {
+ console.log('User logged out');
+ },
+ error: function(data) {
+ console.log('Problem logging user out');
+ }
+ });
+
+
if (callback) {
callback();
} else {
- window.location.hash = Utils.loginHash;
+ window.location.replace(window.location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/?api_server=' + API_SERVER);
}
}
Utils.isNotAuthenticated = function(windowLocation, callback) {