]> git.immae.eu Git - perso/Immae/Projets/Nodejs/Surfer.git/commitdiff
Use accessTokens instead of username/password
authorJohannes Zellner <johannes@cloudron.io>
Thu, 9 Feb 2017 11:40:40 +0000 (12:40 +0100)
committerJohannes Zellner <johannes@cloudron.io>
Thu, 9 Feb 2017 11:40:40 +0000 (12:40 +0100)
frontend/js/app.js
npm-shrinkwrap.json
package.json
server.js
src/auth.js

index 33346aaeb0da5bf10aeee620419336c6a7e6f19a..b07560affa57d8bff120ad7c87d3ac92bbfd12a1 100644 (file)
@@ -1,37 +1,54 @@
 (function () {
 'use strict';
 
+function getProfile(accessToken, callback) {
+    callback = callback || function (error) { if (error) console.error(error); };
+
+    superagent.get('/api/profile').query({ access_token: accessToken }).end(function (error, result) {
+        app.busy = false;
+
+        if (error && !error.response) return callback(error);
+        if (result.statusCode !== 200) {
+            delete localStorage.accessToken;
+            return callback('Invalid access token');
+        }
+
+        localStorage.accessToken = accessToken;
+        app.session.username = result.body.username;
+        app.session.valid = true;
+
+        callback();
+    });
+}
+
 function login(username, password) {
     username = username || app.loginData.username;
     password = password || app.loginData.password;
 
     app.busy = true;
 
-    superagent.get('/api/files/').query({ username: username, password: password }).end(function (error, result) {
+    superagent.post('/api/login').query({ username: username, password: password }).end(function (error, result) {
         app.busy = false;
 
         if (error) return console.error(error);
         if (result.statusCode === 401) return console.error('Invalid credentials');
 
-        app.session.valid = true;
-        app.session.username = username;
-        app.session.password = password;
-
-        // clearly not the best option
-        localStorage.username = username;
-        localStorage.password = password;
+        getProfile(result.body.accessToken, function (error) {
+            if (error) return console.error(error);
 
-        loadDirectory(window.location.hash.slice(1));
+            loadDirectory(window.location.hash.slice(1));
+        });
     });
 }
 
 function logout() {
-    app.session.valid = false;
-    app.session.username = null;
-    app.session.password = null;
+    superagent.post('/api/logout').query({ access_token: localStorage.accessToken }).end(function (error) {
+        if (error) console.error(error);
+
+        app.session.valid = false;
 
-    delete localStorage.username;
-    delete localStorage.password;
+        delete localStorage.accessToken;
+    });
 }
 
 function sanitize(filePath) {
@@ -77,7 +94,7 @@ function loadDirectory(filePath) {
 
     filePath = filePath ? sanitize(filePath) : '/';
 
-    superagent.get('/api/files/' + encode(filePath)).query({ username: app.session.username, password: app.session.password }).end(function (error, result) {
+    superagent.get('/api/files/' + encode(filePath)).query({ access_token: localStorage.accessToken }).end(function (error, result) {
         app.busy = false;
 
         if (result && result.statusCode === 401) return logout();
@@ -138,7 +155,7 @@ function uploadFiles(files) {
         var formData = new FormData();
         formData.append('file', file);
 
-        superagent.post('/api/files' + path).query({ username: app.session.username, password: app.session.password }).send(formData).end(function (error, result) {
+        superagent.post('/api/files' + path).query({ access_token: localStorage.accessToken }).send(formData).end(function (error, result) {
             if (result && result.statusCode === 401) return logout();
             if (result && result.statusCode !== 201) console.error('Error uploading file: ', result.statusCode);
             if (error) console.error(error);
@@ -189,7 +206,7 @@ function del(entry) {
 
     var path = encode(sanitize(app.path + '/' + entry.filePath));
 
-    superagent.del('/api/files' + path).query({ username: app.session.username, password: app.session.password, recursive: true }).end(function (error, result) {
+    superagent.del('/api/files' + path).query({ access_token: localStorage.accessToken, recursive: true }).end(function (error, result) {
         app.busy = false;
 
         if (result && result.statusCode === 401) return logout();
@@ -216,7 +233,7 @@ function rename(data) {
     var path = encode(sanitize(app.path + '/' + data.entry.filePath));
     var newFilePath = sanitize(app.path + '/' + data.newFilePath);
 
-    superagent.put('/api/files' + path).query({ username: app.session.username, password: app.session.password }).send({ newFilePath: newFilePath }).end(function (error, result) {
+    superagent.put('/api/files' + path).query({ access_token: localStorage.accessToken }).send({ newFilePath: newFilePath }).end(function (error, result) {
         app.busy = false;
 
         if (result && result.statusCode === 401) return logout();
@@ -241,7 +258,7 @@ function createDirectory(name) {
 
     var path = encode(sanitize(app.path + '/' + name));
 
-    superagent.post('/api/files' + path).query({ username: app.session.username, password: app.session.password, directory: true }).end(function (error, result) {
+    superagent.post('/api/files' + path).query({ access_token: localStorage.accessToken, directory: true }).end(function (error, result) {
         app.busy = false;
 
         if (result && result.statusCode === 401) return logout();
@@ -327,7 +344,7 @@ var app = new Vue({
 
 window.app = app;
 
-login(localStorage.username, localStorage.password);
+getProfile(localStorage.accessToken);
 
 $(window).on('hashchange', function () {
     loadDirectory(window.location.hash.slice(1));
index 6d34f160891d84a45a40fc3b0281b0792edb4ac6..ff96bbb8bfa965fdb033eb461385eccf40aace78 100644 (file)
         }
       }
     },
+    "passport-http-bearer": {
+      "version": "1.0.1",
+      "from": "passport-http-bearer@latest",
+      "resolved": "https://registry.npmjs.org/passport-http-bearer/-/passport-http-bearer-1.0.1.tgz"
+    },
     "passport-ldapjs": {
       "version": "1.0.2",
       "from": "passport-ldapjs@>=1.0.2 <2.0.0",
         }
       }
     },
+    "passport-strategy": {
+      "version": "1.0.0",
+      "from": "passport-strategy@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz"
+    },
     "readline-sync": {
       "version": "1.4.1",
       "from": "readline-sync@>=1.4.1 <2.0.0",
       "version": "1.8.3",
       "from": "underscore@>=1.8.3 <2.0.0",
       "resolved": "http://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz"
+    },
+    "uuid": {
+      "version": "3.0.1",
+      "from": "uuid@latest",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz"
     }
   }
 }
index 55bfd4bbc426d3bc71e294e813399a7e41f2fb7b..e2e048854c6037377a674f749a50aba0dd949cb6 100644 (file)
     "morgan": "^1.7.0",
     "multiparty": "^4.1.2",
     "passport": "^0.2.2",
+    "passport-http-bearer": "^1.0.1",
     "passport-ldapjs": "^1.0.2",
     "readline-sync": "^1.4.1",
     "request": "^2.69.0",
     "safetydance": "^0.1.1",
     "serve-index": "^1.8.0",
     "superagent": "^1.7.2",
-    "underscore": "^1.8.3"
+    "underscore": "^1.8.3",
+    "uuid": "^3.0.1"
   },
   "devDependencies": {
     "expect.js": "^0.3.1",
index 67cbd915851c530659e4ed7f0e6d881f2b6ed8ba..68fbb9a31a4a1bd3e3ea925e8ecdf6bd2f14751d 100755 (executable)
--- a/server.js
+++ b/server.js
@@ -23,6 +23,9 @@ var router = new express.Router();
 
 var multipart = multipart({ maxFieldsSize: 2 * 1024, limit: '512mb', timeout: 3 * 60 * 1000 });
 
+router.post  ('/api/login', auth.login);
+router.post  ('/api/logout', auth.verify, auth.logout);
+router.get   ('/api/profile', auth.verify, auth.getProfile);
 router.get   ('/api/files/*', auth.verify, files.get);
 router.post  ('/api/files/*', auth.verify, multipart, files.post);
 router.put   ('/api/files/*', auth.verify, files.put);
index b56f09fbc9f736d254689b45a09e73309da69ec2..8645d4cb2291155d62e9c84716d5793d743ec6aa 100644 (file)
@@ -4,10 +4,25 @@ var passport = require('passport'),
     path = require('path'),
     safe = require('safetydance'),
     bcrypt = require('bcryptjs'),
-    LdapStrategy = require('passport-ldapjs').Strategy;
+    uuid = require('uuid/v4'),
+    BearerStrategy = require('passport-http-bearer').Strategy,
+    LdapStrategy = require('passport-ldapjs').Strategy,
+    HttpSuccess = require('connect-lastmile').HttpSuccess;
 
 var LOCAL_AUTH_FILE = path.resolve(process.env.LOCAL_AUTH_FILE || './.users.json');
 
+var gTokenStore = {};
+
+function issueAccessToken() {
+    return function (req, res, next) {
+        var accessToken = uuid();
+
+        gTokenStore[accessToken] = req.user;
+
+        next(new HttpSuccess(201, { accessToken: accessToken, user: req.user }));
+    };
+}
+
 passport.serializeUser(function (user, done) {
     console.log('serializeUser', user);
     done(null, user.uid);
@@ -24,20 +39,28 @@ var LDAP_USERS_BASE_DN = process.env.LDAP_USERS_BASE_DN;
 if (LDAP_URL && LDAP_USERS_BASE_DN) {
     console.log('Enable ldap auth');
 
-    exports.verify = passport.authenticate('ldap');
+    exports.login = [ passport.authenticate('ldap'), issueAccessToken() ];
 } else {
     console.log('Use local user file:', LOCAL_AUTH_FILE);
 
-    exports.verify = function (req, res, next) {
-        var users = safe.JSON.parse(safe.fs.readFileSync(LOCAL_AUTH_FILE));
-        if (!users) return res.send(401);
-        if (!users[req.query.username]) return res.send(401);
+    exports.login = [
+        function (req, res, next) {
+            var users = safe.JSON.parse(safe.fs.readFileSync(LOCAL_AUTH_FILE));
+            if (!users) return res.send(401);
+            if (!users[req.query.username]) return res.send(401);
 
-        bcrypt.compare(req.query.password, users[req.query.username].passwordHash, function (error, valid) {
-            if (error || !valid) return res.send(401);
-            next();
-        });
-    };
+            bcrypt.compare(req.query.password, users[req.query.username].passwordHash, function (error, valid) {
+                if (error || !valid) return res.send(401);
+
+                req.user = {
+                    username: req.query.username
+                };
+
+                next();
+            });
+        },
+        issueAccessToken()
+    ];
 }
 
 var opts = {
@@ -58,3 +81,23 @@ var opts = {
 passport.use(new LdapStrategy(opts, function (profile, done) {
     done(null, profile);
 }));
+
+exports.verify = passport.authenticate('bearer', { session: false });
+
+passport.use(new BearerStrategy(function (token, done) {
+    if (!gTokenStore[token]) return done(null, false);
+
+    return done(null, gTokenStore[token], { accessToken: token });
+}));
+
+exports.logout = function (req, res, next) {
+    console.log(req.authInfo);
+
+    delete gTokenStore[req.authInfo.accessToken];
+
+    next(new HttpSuccess(200, {}));
+};
+
+exports.getProfile = function (req, res, next) {
+    next(new HttpSuccess(200, { username: req.user.username }));
+};