diff --git a/src/app/Entries/newEntry.vue b/src/app/Entries/newEntry.vue index 21e2b28..3ea0b32 100644 --- a/src/app/Entries/newEntry.vue +++ b/src/app/Entries/newEntry.vue @@ -177,7 +177,6 @@ logging.success(this.$t('entries.entry_created')); }) .catch((err) => { - console.log(err); logging.error(this.$t('entries.error_creation')); }); }, diff --git a/src/main.js b/src/main.js index aaaf136..7721975 100644 --- a/src/main.js +++ b/src/main.js @@ -1,3 +1,17 @@ import 'vue'; import './locales'; import './router'; +import http from './services/http'; + + +window.setInterval(() => { + let token = localStorage.getItem('token'); + if (token) { + http.auth.refreshToken(token).then((new_token) => { + localStorage.setItem('token', new_token) + }) + } +}, 60 * 60 * 1000); + + + diff --git a/src/router.js b/src/router.js index 851c55b..408a5a2 100644 --- a/src/router.js +++ b/src/router.js @@ -7,7 +7,7 @@ import LandingPage from './landing-page/LandingPage'; import LoginPage from './app/Login'; import RegisterPage from './app/Register'; import LessPassConnected from './app/Index'; -import Http from './services/http'; +import http from './services/http'; const router = new Router({ history: true, @@ -25,7 +25,7 @@ router.map({ component: RegisterPage, }, '/app/': { - auth_required: false, + auth_required: true, component: LessPassConnected, }, }); @@ -34,18 +34,22 @@ router.redirect({ '*': '/', }); -Vue.config.debug = true; - router.beforeEach(transition => { - if (transition.to.path === '/' && Http.auth.user.authenticated) { - transition.redirect('/app/'); - } - - if (transition.to.auth_required && !Http.auth.user.authenticated) { - transition.redirect('/login/'); + if (transition.to.auth_required) { + http.auth.checkAuth() + .then(() => { + if (transition.to.path === '/') { + transition.redirect('/app/'); + } else { + transition.next(); + } + }) + .catch(() => { + transition.redirect('/login/'); + }); + } else { + transition.next(); } - - transition.next(); }); diff --git a/src/services/auth.js b/src/services/auth.js index 5de771f..3f63207 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -1,23 +1,17 @@ import request from 'axios'; -export default class Auth { - constructor(localStorage = global.localStorage) { - this.localStorage = localStorage; - this.user = { - authenticated: false, - } - } - +export default { + localStorage: null, + user: { + authenticated: false, + }, login(credentials) { - var self = this; return request.post('/api/token-auth/', credentials) .then((response) => { - self.localStorage.setItem('token', response.data.token); - self.user.authenticated = true; + this.authUser(response.data.token); return response; }); - } - + }, refreshToken(token) { return request .post('/api/token-refresh/', {token: token}) @@ -27,36 +21,50 @@ export default class Auth { .catch((err) => { throw err; }); - } - + }, + getToken(token_name){ + return new Promise((resolve, reject) => { + const token = this.localStorage.getItem(token_name); + if (token) { + resolve(token); + } else { + reject(); + } + }); + }, + verifyToken(token){ + return request.post('/api/token-verify/', {token: token}) + .then(() => { + return token + }); + }, + gu() { + return this.user.authenticated + }, + authUser(token){ + this.localStorage.setItem('token', token); + this.user.authenticated = true; + }, + resetAuth(e){ + this.localStorage.removeItem('token'); + this.user.authenticated = false; + throw err; + }, checkAuth() { - var self = this; - const token = self.localStorage.getItem('token'); - if (token) { - return request - .post('/api/token-verify/', {token: token}) - .then((response) => { - self.user.authenticated = true; - return response; - }) - .catch(() => { - self.user.authenticated = false; - self.localStorage.removeItem('token'); - throw err; - }); - } - } - + return this.getToken('token') + .then(this.verifyToken) + .then(this.authUser.bind(this)) + .catch(this.resetAuth.bind(this)); + }, logout() { - var self = this; return new Promise((resolve, reject) => { try { - self.localStorage.removeItem('token'); - self.user.authenticated = false; + this.localStorage.removeItem('token'); + this.user.authenticated = false; resolve(); } catch (e) { reject(e); } }); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/services/entries.js b/src/services/entries.js index a3008fb..a161ffb 100644 --- a/src/services/entries.js +++ b/src/services/entries.js @@ -1,22 +1,22 @@ -import axios from 'axios'; +import request from 'axios'; -export default class Entry { - constructor(localStorage = global.localStorage) { - this.localStorage = localStorage; - this.request = axios.create({ +export default { + localStorage: null, + getRequestConfig() { + return { headers: {'Authorization': 'JWT ' + this.localStorage.getItem('token')} - }); - } - + } + }, create(entry) { - return this.request.post('/api/entries/', entry) + let config = this.getRequestConfig(); + return request.post('/api/entries/', entry, config) .then((response) => { return response; }); - } - + }, all() { - return this.request.get('/api/entries/') + let config = this.getRequestConfig(); + return request.get('/api/entries/', config) .then((response) => { return response; }); diff --git a/src/services/http.js b/src/services/http.js index 36d9a98..5a2a3ef 100644 --- a/src/services/http.js +++ b/src/services/http.js @@ -1,14 +1,11 @@ -import AuthService from './auth'; -import EntryServices from './entries'; +import auth from './auth'; +import entries from './entries'; -let Auth = new AuthService(); -let Entries = new EntryServices(); - -export {Auth}; -export {Entries}; +auth.localStorage = localStorage; +entries.localStorage = localStorage; export default { - auth: Auth, - entries: Entries + auth: auth, + entries: entries } diff --git a/tests/auth.tests.js b/tests/auth.tests.js new file mode 100644 index 0000000..1a633d4 --- /dev/null +++ b/tests/auth.tests.js @@ -0,0 +1,115 @@ +import auth from '../src/services/auth'; + +import {localStorage} from './helpers'; +auth.localStorage = localStorage; + +suite('auth', () => { + var token, credentials; + + beforeEach(() => { + credentials = { + email: 'test@lesspass.com', + password: 'password' + }; + token = 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9'; + }); + + test('should make a post request to create a session', (done) => { + nock('http://localhost/').post('/api/token-auth/', credentials).reply(201, {token: token}); + auth.login(credentials).then(() => { + done(); + }); + }); + + test('should throw error if bad request', (done) => { + nock('http://localhost/').post('/api/token-auth/', credentials).reply(400, {}); + auth.login(credentials).catch(() => { + done(); + }); + }); + + test('should store token in localStorage', (done) => { + nock('http://localhost/').post('/api/token-auth/', credentials).reply(201, {token: token}); + auth.login(credentials).then(() => { + assert.equal(token, localStorage.getItem('token')); + done(); + }); + }); + + test('should authenticate the user', (done) => { + nock('http://localhost/').post('/api/token-auth/', credentials).reply(201, {token: token}); + auth.login(credentials).then(() => { + assert(auth.user.authenticated); + done(); + }); + }); + + test('check token with a valid token', (done) => { + nock('http://localhost/').post('/api/token-verify/', {"token": token}).reply(200); + localStorage.setItem('token', token); + auth.checkAuth().then(() => { + assert(auth.user.authenticated); + done(); + }); + }); + + test('check token with an invalid token', (done) => { + nock('http://localhost/').post('/api/token-verify/', {"token": token}).reply(400); + localStorage.setItem('token', token); + auth.checkAuth().catch(() => { + assert(!auth.user.authenticated); + done(); + }); + }); + + test('check auth without any token', (done) => { + auth.user.authenticated = true; + localStorage.removeItem('token'); + auth.checkAuth().catch(() => { + assert(!auth.user.authenticated); + done(); + }); + }); + + test('get token', (done) => { + localStorage.setItem('lesspass-token', token); + auth.getToken('lesspass-token').then((expected_token) => { + assert.equal(token, expected_token); + localStorage.removeItem('lesspass-token'); + done(); + }); + }); + + test('get missing token', (done) => { + localStorage.removeItem('token'); + auth.getToken('token').catch(() => { + done(); + }); + }); + + test('check refresh token non-expired', (done) => { + var new_token = 'wibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9eyJzdWIiOiIxMjM0NTY3ODkwIi'; + nock('http://localhost/').post('/api/token-refresh/', {"token": token}).reply(200, {token: new_token}); + auth.refreshToken(token).then((t) => { + assert.equal(new_token, t); + done(); + }); + }); + + test('check refresh token expired', (done) => { + localStorage.setItem('token', token); + nock('http://localhost/').post('/api/token-refresh/', {"token": token}).reply(400); + auth.refreshToken(token).catch(() => { + done(); + }); + }); + + test('logout', (done) => { + auth.user.authenticated = true; + auth.logout().then(()=> { + assert(!auth.user.authenticated); + assert(localStorage.getItem('token') === null); + done(); + }); + }); +}); \ No newline at end of file diff --git a/tests/entries.tests.js b/tests/entries.tests.js new file mode 100644 index 0000000..8de522a --- /dev/null +++ b/tests/entries.tests.js @@ -0,0 +1,63 @@ +import entries from '../src/services/entries'; + +import {localStorage} from './helpers'; +entries.localStorage = localStorage; + +suite('entries', () => { + var entry, token; + + beforeEach(() => { + token = 'ZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFt'; + localStorage.setItem('token', token); + entry = { + "site": "twitter.com", + "password": { + "counter": 1, + "settings": [ + "lowercase", + "uppercase", + "numbers", + "symbols" + ], + "length": 12 + }, + "email": "guillaume@oslab.fr", + }; + }); + + test('should send requests with Authorization header', (done) => { + var headers = {reqheaders: {'Authorization': 'JWT ' + token}}; + nock('http://localhost/', headers).get('/api/entries/').reply(200, {entries: []}); + entries.all().then(() => { + done(); + }); + }); + + test('should send requests with Authorization header updated', (done) => { + var new_token = 'WV9eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRyd'; + localStorage.setItem('token', new_token); + var headers = {reqheaders: {'Authorization': 'JWT ' + new_token}}; + nock('http://localhost/', headers).get('/api/entries/').reply(200, {entries: []}); + entries.all().then(() => { + done(); + }); + }); + + test('should make a post request to create an entry', (done) => { + nock('http://localhost/').post('/api/entries/', entry).reply(201, {}); + entries.create(entry) + .then((response) => { + assert.equal(201, response.status); + done(); + }); + }); + + test('should get all entries', (done) => { + nock('http://localhost/').get('/api/entries/').reply(200, {entries: []}); + entries.all().then((response) => { + assert.equal(200, response.status); + assert.equal(0, response.data.entries.length); + done(); + }); + }); +}); diff --git a/tests/helpers.js b/tests/helpers.js new file mode 100644 index 0000000..7dd1958 --- /dev/null +++ b/tests/helpers.js @@ -0,0 +1,14 @@ +import assert from 'assert'; +import nock from 'nock'; + +global.assert = assert; +global.nock = nock; + +import {LocalStorage} from 'node-localstorage'; +const localStorage = new LocalStorage('./tests/localStorage'); + +export {localStorage}; + +after(()=> { + localStorage._deleteLocation() +}); \ No newline at end of file diff --git a/tests/services.tests.js b/tests/services.tests.js deleted file mode 100644 index 2507f89..0000000 --- a/tests/services.tests.js +++ /dev/null @@ -1,157 +0,0 @@ -import assert from 'assert'; -import nock from 'nock'; - - -import Auth from '../src/services/auth'; -import Entries from '../src/services/entries'; -import {LocalStorage} from 'node-localstorage'; - -const url = 'http://localhost/'; - -suite('auth', () => { - var auth, localStorage, token, credentials; - - beforeEach(() => { - credentials = { - email: 'test@lesspass.com', - password: 'password' - }; - token = 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9'; - localStorage = new LocalStorage('./tests/localStorage'); - auth = new Auth(localStorage); - }); - - test('should make a post request to create a session', (done) => { - nock(url).post('/api/token-auth/', credentials).reply(201, {token: token}); - auth.login(credentials).then(() => { - done(); - }); - }); - - test('should throw error if bad request', (done) => { - nock(url).post('/api/token-auth/', credentials).reply(400, {}); - auth.login(credentials).catch(() => { - done(); - }); - }); - - test('should store token in localStorage', (done) => { - nock(url).post('/api/token-auth/', credentials).reply(201, {token: token}); - auth.login(credentials).then(() => { - assert.equal(token, localStorage.getItem('token')); - done(); - }); - }); - - test('should authenticate the user', (done) => { - nock(url).post('/api/token-auth/', credentials).reply(201, {token: token}); - auth.login(credentials).then(() => { - assert(auth.user.authenticated); - done(); - }); - }); - - test('check auth with a valid token', (done) => { - nock(url).post('/api/token-verify/', {"token": token}).reply(200); - localStorage.setItem('token', token); - auth.checkAuth().then(() => { - assert(auth.user.authenticated); - done(); - }); - }); - - test('check auth with an invalid token', (done) => { - nock(url).post('/api/token-verify/', {"token": token}).reply(400); - localStorage.setItem('token', token); - auth.checkAuth().catch(() => { - assert(!auth.user.authenticated); - done(); - }); - }); - - test('check refresh token non-expired', (done) => { - var new_token = 'wibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9eyJzdWIiOiIxMjM0NTY3ODkwIi'; - nock(url).post('/api/token-refresh/', {"token": token}).reply(200, {token: new_token}); - auth.refreshToken(token).then((t) => { - assert.equal(new_token, t); - done(); - }); - }); - - test('check refresh token expired', (done) => { - localStorage.setItem('token', token); - nock(url).post('/api/token-refresh/', {"token": token}).reply(400); - auth.refreshToken(token).catch(() => { - done(); - }); - }); - - test('logout', (done) => { - auth.user.authenticated = true; - auth.logout().then(()=> { - assert(!auth.user.authenticated); - assert(localStorage.getItem('token') === null); - done(); - }); - }); - - after(()=> { - localStorage._deleteLocation() - }); -}); - - -suite('entries', () => { - var entries, entry, localStorage, token; - - beforeEach(() => { - token = 'ZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFt'; - localStorage = new LocalStorage('./tests/localStorageEntries'); - localStorage.setItem('token', token); - entries = new Entries(localStorage); - entry = { - "site": "twitter.com", - "password": { - "counter": 1, - "settings": [ - "lowercase", - "uppercase", - "numbers", - "symbols" - ], - "length": 12 - }, - "email": "guillaume@oslab.fr", - }; - }); - - test('should send requests with Authorization header', (done) => { - var headers = {reqheaders: {'Authorization': 'JWT ' + token}}; - nock(url, headers).get('/api/entries/').reply(200, {entries: []}); - entries.all().then(() => { - done(); - }); - }); - - test('should make a post request to create an entry', (done) => { - nock(url).post('/api/entries/', entry).reply(201, {}); - entries.create(entry) - .then((response) => { - assert.equal(201, response.status); - done(); - }); - }); - - test('should get all entries', (done) => { - nock(url).get('/api/entries/').reply(200, {entries: []}); - entries.all().then((response) => { - assert.equal(200, response.status); - assert.equal(0, response.data.entries.length); - done(); - }); - }); - - after(()=> { - localStorage._deleteLocation() - }); -}); \ No newline at end of file