From 53a5daea6ec1d3e868974fe4c132bdebbbe8a943 Mon Sep 17 00:00:00 2001
From: Moritz Schneider <scmoritz@student.ethz.ch>
Date: Sun, 29 Oct 2017 18:02:12 +0100
Subject: [PATCH] Added login

---
 .eslintrc.js            |   1 +
 src/index.js            |  10 +++-
 src/layout.js           |  16 ------
 src/models/auth.js      | 106 ++++++++++++++++++++++++++++++++++++++++
 src/models/config.js    |   7 +++
 src/models/log.js       |   9 ++++
 src/{ => views}/amiv.js |   0
 src/views/layout.js     |  37 ++++++++++++++
 src/views/login.js      |  31 ++++++++++++
 9 files changed, 199 insertions(+), 18 deletions(-)
 delete mode 100644 src/layout.js
 create mode 100644 src/models/auth.js
 create mode 100644 src/models/config.js
 create mode 100644 src/models/log.js
 rename src/{ => views}/amiv.js (100%)
 create mode 100644 src/views/layout.js
 create mode 100644 src/views/login.js

diff --git a/.eslintrc.js b/.eslintrc.js
index 2a3a7f3b..4c4cab19 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -7,5 +7,6 @@ module.exports = {
     "rules": {
         "no-multi-str": 0,
         "no-underscore-dangle": 0,
+        "no-console": 0
     },
 };
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 80b1d588..38feb4a3 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,7 +1,8 @@
 // src/index.js
 const m = require('mithril');
-const Layout = require('./layout');
-const amiv = require('./amiv');
+const Layout = require('./views/layout');
+const amiv = require('./views/amiv');
+const login = require('./views/login');
 
 m.route(document.body, '/', {
   '/': {
@@ -9,4 +10,9 @@ m.route(document.body, '/', {
       return m(Layout, m(amiv));
     },
   },
+  '/login': {
+    render() {
+      return m(Layout, m(login));
+    },
+  },
 });
diff --git a/src/layout.js b/src/layout.js
deleted file mode 100644
index c6ba9c15..00000000
--- a/src/layout.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const m = require('mithril');
-
-module.exports = {
-  view(vnode) {
-    return m('div', [
-      m('nav', [
-        m('a', { href: '/' }, 'AMIV'),
-        m('a', { href: '/events' }, 'Events'),
-        m('a', { href: '/studydocuments' }, 'Studienunterlagen'),
-        m('a', { href: '/jobs' }, 'Jobs'),
-        m('a', { href: '/profile' }, 'Profil'),
-      ]),
-      m('main', vnode.children),
-    ]);
-  },
-};
diff --git a/src/models/auth.js b/src/models/auth.js
new file mode 100644
index 00000000..198dde98
--- /dev/null
+++ b/src/models/auth.js
@@ -0,0 +1,106 @@
+const m = require('mithril');
+const Config = require('./config');
+const log = require('./log');
+
+const auth = {
+  username: '',
+  token: '',
+  etag: '',
+  error: '',
+  id: '',
+  authenticated: false,
+  lastChecked: 0,
+  login(username, password) {
+    this.reloadLocalStorage();
+    return m.request({
+      method: 'POST',
+      url: `${Config.api_url}/sessions`,
+      data: { username, password },
+    }).then((result) => {
+      const dt = new Date();
+      log.log('logged in!');
+      this.token = result.token;
+      this.etag = result._etag;
+      this.id = result._id;
+      this.authenticated = true;
+      this.username = username;
+      localStorage.setItem('token', result.token);
+      localStorage.setItem('username', username);
+      localStorage.setItem('id', result._id);
+      localStorage.setItem('etag', result._etag);
+      this.lastChecked = dt.getTime();
+      m.route.set('/');
+    }).catch((e) => {
+      this.error = e.message;
+    });
+  },
+  logout() {
+    this.reloadLocalStorage();
+    this.authenticated = false;
+    return m.request({
+      method: 'DELETE',
+      url: `${Config.api_url}/sessions/${this.id}`,
+      headers: {
+        Authorization: `Token ${this.token}`,
+        'If-Match': this.etag,
+      },
+    }).then(() => {
+      log.log('logged out!');
+      this.token = '';
+      this.authenticated = false;
+      this.error = '';
+      localStorage.removeItem('token');
+      localStorage.removeItem('username');
+      localStorage.removeItem('id');
+      localStorage.removeItem('etag');
+      m.route.set('/login');
+    }).catch((e) => {
+      this.error = e.message;
+      this.authenticated = false;
+      m.route.set('/login');
+    });
+  },
+  checkLogin() {
+    const dt = new Date();
+    auth.reloadLocalStorage();
+    if (this.authenticated === true) {
+      log.log('no session found');
+      m.route.set('/login');
+      return new Promise(() => { });
+    }
+    if (dt.getTime() > this.lastChecked + 5000) {
+      return m.request({
+        method: 'GET',
+        url: `${Config.api_url}/sessions/${this.token}`,
+      }).then((result) => {
+        const dt2 = new Date();
+        log.log('session is still valid!');
+        this.authenticated = true;
+        this.etag = result._etag;
+        this.lastChecked = dt2.getTime();
+      }).catch((e) => {
+        log.log('token is not valid');
+        log.log(e);
+        this.authenticated = false;
+        localStorage.removeItem('session');
+        localStorage.removeItem('username');
+        localStorage.removeItem('id');
+        localStorage.removeItem('etag');
+        m.route.set('/login');
+      });
+    }
+    return new Promise(() => { });
+  },
+  reloadLocalStorage() {
+    log.log('checking stored session');
+    if (localStorage.getItem('token') !== null) {
+      this.token = localStorage.token;
+      this.id = localStorage.id;
+      this.username = localStorage.username;
+      this.etag = localStorage.etag;
+      this.authenticated = true;
+    }
+  },
+};
+
+module.exports = auth;
diff --git a/src/models/config.js b/src/models/config.js
new file mode 100644
index 00000000..a607d5e3
--- /dev/null
+++ b/src/models/config.js
@@ -0,0 +1,7 @@
+
+const Config = {
+  api_url: 'https://amiv-api.ethz.ch',
+  verbose: true,
+};
+
+module.exports = Config;
diff --git a/src/models/log.js b/src/models/log.js
new file mode 100644
index 00000000..d9e3b19c
--- /dev/null
+++ b/src/models/log.js
@@ -0,0 +1,9 @@
+const config = require('./config.js');
+
+const log = {
+  log(message) {
+    if (config.verbose === true) console.log(message);
+  },
+};
+
+module.exports = log;
diff --git a/src/amiv.js b/src/views/amiv.js
similarity index 100%
rename from src/amiv.js
rename to src/views/amiv.js
diff --git a/src/views/layout.js b/src/views/layout.js
new file mode 100644
index 00000000..50c9acba
--- /dev/null
+++ b/src/views/layout.js
@@ -0,0 +1,37 @@
+const m = require('mithril');
+const auth = require('../models/auth');
+
+module.exports = {
+  oninit: auth.checkLogin,
+  view(vnode) {
+    if (auth.authenticated === false) {
+      return m('div', [
+        m('nav', [
+          m('a', { href: '/' }, 'AMIV'),
+          m('a', { href: '/#!/events' }, 'Events'),
+          m('a', { href: '/#!/studydocuments' }, 'Studienunterlagen'),
+          m('a', { href: '/#!/jobs' }, 'Jobs'),
+          m('a', { href: '/#!/login' }, 'Login'),
+        ]),
+        m('main', vnode.children),
+      ]);
+    }
+    return m('div', [
+      m('nav', [
+        m('a', { href: '/' }, 'AMIV'),
+        m('a', { href: '/#!/events' }, 'Events'),
+        m('a', { href: '/#!/studydocuments' }, 'Studienunterlagen'),
+        m('a', { href: '/#!/jobs' }, 'Jobs'),
+        m('a', { href: '/#!/profile' }, 'Profil'),
+        m('a', {
+          href: '#',
+          onclick: () => {
+            auth.logout();
+            return false;
+          },
+        }, 'Logout'),
+      ]),
+      m('main', vnode.children),
+    ]);
+  },
+};
diff --git a/src/views/login.js b/src/views/login.js
new file mode 100644
index 00000000..e1fd04b4
--- /dev/null
+++ b/src/views/login.js
@@ -0,0 +1,31 @@
+const m = require('mithril');
+const Auth = require('../models/auth');
+
+module.exports = {
+  username: '',
+  password: '',
+  view() {
+    return m('div', [
+      m(
+        'form', {
+          onsubmit: (e) => {
+            e.preventDefault();
+            Auth.login(this.username, this.password);
+          },
+        },
+        m('h3', 'Login'), [
+          m('p', Auth.error),
+          m('input.input[type=text][placeholder=Username]', {
+            oninput: m.withAttr('value', (value) => { this.username = value; }),
+            value: this.username,
+          }),
+          m('input.input[placeholder=Password][type=password]', {
+            oninput: m.withAttr('value', (value) => { this.password = value; }),
+            value: this.password,
+          }),
+          m('button.button[type=submit]', 'Login'),
+        ],
+      ),
+    ]);
+  },
+};
-- 
GitLab