Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions controllers/jwtHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const crypto = require('crypto');
var jwt = require('jsonwebtoken');
var moment = require('moment');
var { v4: uuidv4 } = require('uuid');

var refreshTokens = Object.create(null);

const jwtSignOptions = {
algorithm: "HS384"
};

const hashJWT = function hashJWT (jwtString) {
if (typeof jwtString !== 'string') {
throw new Error('method hashJWT expects a string parameter');
}

return crypto
.createHmac('sha256', 'I ALSO WANT TO BE CHANGED')
.update(jwtString)
.digest('base64');
};


const createToken = function createToken (payload) {
payload.jwtid = uuidv4();
return new Promise(function (resolve, reject) {
jwt.sign(payload, process.env.JWT_SECRET, async (err, token) => {
if (err) {
return reject(err);
}

try {
// JWT generation was successful
// we now create the refreshToken.
// and set the refreshTokenExpires to 1 week
// it is a HMAC of the jwt string
const refreshToken = hashJWT(token);
addRefreshToken(refreshToken);

return resolve({ token, refreshToken });
} catch (err) {
return reject(err);
}
});
});
};

const useRefreshToken = function useRefreshToken (refreshToken) {
const now = Date.now() / 1000;
if (typeof refreshTokens[refreshToken] !== 'undefined' && refreshTokens[refreshToken].exp > now) {
refreshTokens[refreshToken] = undefined;
return true;
}
return false;
}

const addRefreshToken = function addRefreshToken (refreshToken) {
refreshTokens[refreshToken] = {
exp: moment().add(7, 'days').unix()
};
}

module.exports = { createToken, hashJWT, useRefreshToken, addRefreshToken };
60 changes: 60 additions & 0 deletions controllers/jwtTokenBlacklist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const moment = require('moment'),
{ hashJWT } = require('./jwtHelper');

// our token blacklist is just a js object with
// jtis as keys and all claims as values
const tokenBlacklist = Object.create(null);

const cleanupExpiredTokens = function cleanupExpiredTokens () {
const now = Date.now() / 1000;
for (const jti of Object.keys(tokenBlacklist)) {
if (tokenBlacklist[jti].exp < now) {
delete tokenBlacklist[jti];
}
}
};

const isTokenBlacklisted = function isTokenBlacklisted (token, tokenString) {
cleanupExpiredTokens();

if (!token.jti) { // token has no id.. -> shouldn't be accepted
return true;
}

const hash = hashJWT(tokenString);

if (typeof tokenBlacklist[hash] !== 'undefined') {
return true;
}

return false;
};

const addTokenToBlacklist = function addTokenToBlacklist (token, tokenString) {
cleanupExpiredTokens();

const hash = hashJWT(tokenString);

if (token && token.jti) {
tokenBlacklist[hash] = token;
}
};

const addTokenHashToBlacklist = function addTokenHashToBlacklist (tokenHash) {
cleanupExpiredTokens();

if (typeof tokenHash === 'string') {
// just set the exp claim to now plus one week to be sure
tokenBlacklist[tokenHash] = {
exp: moment.utc()
.add(1, 'week')
.unix()
};
}
};

module.exports = {
isTokenBlacklisted,
addTokenToBlacklist,
addTokenHashToBlacklist
};
9 changes: 6 additions & 3 deletions controllers/login.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
var colors = require('colors');
var moment = require('moment');
var jwt = require('jsonwebtoken');
const { createToken } = require('./jwtHelper');


// LOGIN
exports.request = function(req, res) {
exports.request = async function(req, res) {

// Authentication
if(process.env.ADMIN_USERNAME === req.body.username && process.env.ADMIN_PASSWORD === req.body.password){
Expand All @@ -16,10 +16,13 @@ exports.request = function(req, res) {
iat: moment().unix(),
exp: moment().add(1, 'days').unix()
};
const { token, refreshToken } = await createToken(payload);

// Create JWT
var result = {
username: process.env.ADMIN_USERNAME,
token: jwt.sign(payload, process.env.JWT_SECRET)
token: token,
refreshToken: refreshToken
};
res.status(200).send(result);
} else {
Expand Down
47 changes: 47 additions & 0 deletions controllers/refreshToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
var colors = require('colors');
const { createToken, useRefreshToken } = require('./jwtHelper');
const { addTokenHashToBlacklist } = require('./jwtTokenBlacklist');
var moment = require('moment');

const refreshJwt = async function refreshJwt (oldRefreshToken) {
return new Promise(async function (resolve, reject) { // TODO: doesnt need to be promise anymore
if(useRefreshToken(oldRefreshToken)) {
// invalidate old token
addTokenHashToBlacklist(oldRefreshToken);

payload = {
iss: process.env.SERVER_URL,
sub: 'Login by username and password',
username: process.env.ADMIN_USERNAME,
iat: moment().unix(),
exp: moment().add(1, 'days').unix()
};

const { token, refreshToken: newRefreshToken } = await createToken(payload);


resolve({ token, refreshToken: newRefreshToken });
} else {
var err = "Refresh token not found or expired. Please sign in with your username and password.";
reject(err);
}
})

};

// LOGIN
exports.request = async function(req, res) {
refreshJwt(req.body.refresh)
.then(({ token, refreshToken }) => {
// Create JWT
var result = {
username: process.env.ADMIN_USERNAME,
token: token,
refreshToken: refreshToken
};
res.status(200).send(result);
}, (err) => {
console.error(colors.red(err));
res.status(301).send(err); // TODO: also catch 500 and so on
});
};
21 changes: 19 additions & 2 deletions public/creator/js/controllers/location/createController.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
var app = angular.module("ive");

// Location create controller
app.controller("locationCreateController", function($scope, $rootScope, $routeParams, $filter, $translate, $location, config, $window, $authenticationService, $locationService) {
app.controller("locationCreateController", function($http, $scope, $rootScope, $routeParams, $filter, $translate, $location, config, $window, $authenticationService, $locationService) {

/*************************************************
FUNCTIONS
Expand Down Expand Up @@ -39,7 +39,24 @@ app.controller("locationCreateController", function($scope, $rootScope, $routePa
$scope.redirect("/locations/" + new_location.location_id);
})
.catch(function onError(response) {
$window.alert(response.data);
if (response.data == "Token expired!") {
$http.post(config.getApiEndpoint() + "/refreshToken", { refresh: $authenticationService.getRefreshToken() })
.then(res => {
$authenticationService.updateUser(res.data);
$locationService.create($scope.location)
.then(function onSuccess(response) {
var new_location = response.data;
$scope.redirect("/locations/" + new_location.location_id);
})
.catch(function onError(response) {
if (response.status > 0) {
$window.alert(response.data);
}
});
})
} else {
$window.alert(response.data);
}
});
}
};
Expand Down
20 changes: 18 additions & 2 deletions public/creator/js/controllers/location/deleteController.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
var app = angular.module("ive");

// Location delete controller
app.controller("locationDeleteController", function($scope, $rootScope, $routeParams, $filter, $translate, $location, config, $window, $authenticationService, $locationService) {
app.controller("locationDeleteController", function($http, $scope, $rootScope, $routeParams, $filter, $translate, $location, config, $window, $authenticationService, $locationService) {

/*************************************************
FUNCTIONS
Expand All @@ -28,7 +28,23 @@ app.controller("locationDeleteController", function($scope, $rootScope, $routePa
$scope.redirect("/locations");
})
.catch(function onError(response) {
$window.alert(response.data);
if (response.data == "Token expired!") {
$http.post(config.getApiEndpoint() + "/refreshToken", { refresh: $authenticationService.getRefreshToken() })
.then(res => {
$authenticationService.updateUser(res.data);
$locationService.remove($scope.location.location_id)
.then(function onSuccess(response) {
$scope.redirect("/locations");
})
.catch(function onError(response) {
if (response.status > 0) {
$window.alert(response.data);
}
});
})
} else {
$window.alert(response.data);
}
});
};

Expand Down
21 changes: 19 additions & 2 deletions public/creator/js/controllers/location/editController.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
var app = angular.module("ive");

// Location edit controller
app.controller("locationEditController", function($scope, $rootScope, $routeParams, $filter, $translate, $location, config, $window, $authenticationService, $locationService) {
app.controller("locationEditController", function($http, $scope, $rootScope, $routeParams, $filter, $translate, $location, config, $window, $authenticationService, $locationService) {

/*************************************************
FUNCTIONS
Expand Down Expand Up @@ -39,7 +39,24 @@ app.controller("locationEditController", function($scope, $rootScope, $routePara
$scope.redirect("/locations/" + $scope.location.location_id);
})
.catch(function onError(response) {
$window.alert(response.data);
if (response.data == "Token expired!") {
$http.post(config.getApiEndpoint() + "/refreshToken", { refresh: $authenticationService.getRefreshToken() })
.then(res => {
$authenticationService.updateUser(res.data);
$locationService.edit($scope.location.location_id, $scope.location)
.then(function onSuccess(response) {
$scope.location = response.data;
$scope.redirect("/locations/" + $scope.location.location_id);
})
.catch(function onError(response) {
if (response.status > 0) {
$window.alert(response.data);
}
});
})
} else {
$window.alert(response.data);
}
});
}
};
Expand Down
Loading