Skip to content

Commit

Permalink
✨ Separation of voting session time and bearer token lifetime (#407)
Browse files Browse the repository at this point in the history
Parent issue: sequentech/meta#762
  • Loading branch information
Findeton committed Aug 8, 2024
1 parent 6f6a2cc commit a8a38a0
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 92 deletions.
158 changes: 112 additions & 46 deletions avRegistration/auth-method-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,38 @@ angular.module('avRegistration')
authmethod.captcha_status = "";
authmethod.admin = false;

authmethod.decodeToken = function(token) {
var parts = token.split("///");
if (parts.length !== 2) {
throw new Error("Invalid token format");
}

var messagePart = parts[1];
var messageComponents = messagePart.split("/");

if (messageComponents.length !== 2) {
throw new Error("Invalid message format");
}

var message = messageComponents[1];
var subParts = message.split(":");

if (subParts.length < 4) {
throw new Error("Invalid message format");
}

var subMessage = subParts.slice(0, subParts.length - 3).join(":");
var expiryTimestamp = parseInt(subParts[subParts.length - 3], 10);
var createTimestamp = parseInt(subParts[subParts.length - 1], 10);

return {
message: subMessage,
create_timestamp: createTimestamp,
expiry_timestamp: expiryTimestamp,
expiry_secs_diff: expiryTimestamp - createTimestamp
};
};

authmethod.getAuthevent = function() {
var adminId = ConfigService.freeAuthId + '';
var href = $location.path();
Expand Down Expand Up @@ -70,13 +102,33 @@ angular.module('avRegistration')
});
}

// Function to get the difference in seconds between two Date objects
function getSecondsDifference(date1, date2) {
var millisecondsDifference = Math.abs(date2 - date1);
var secondsDifference = millisecondsDifference / 1000.0;
return secondsDifference;
function getAllTokens(isAdmin) {
var credentialsStr = $window.sessionStorage.getItem("vote_permission_tokens");
var tokens = [];
if (credentialsStr) {
var credentials = JSON.parse(credentialsStr);
tokens = credentials.map(function (credential) { return credential.token; });
return tokens;
}
if (isAdmin && $http.defaults.headers.common.Authorization) {
tokens.push($http.defaults.headers.common.Authorization);
}
return tokens;
}

function hasPassedHalfLifeExpiry(now, isAdmin) {
var tokens = getAllTokens(isAdmin);
if (0 === tokens.length) {
return false;
}
var halfLifes = tokens.map(function (token) {
var decodedToken = authmethod.decodeToken(token);
return 1000 * (decodedToken.expiry_timestamp + decodedToken.create_timestamp)/2;
});
var minHalfLife = Math.min.apply(null, halfLifes);
return minHalfLife < now;
}

authmethod.setAuth = function(auth, isAdmin, autheventid) {
authmethod.admin = isAdmin;
$http.defaults.headers.common.Authorization = auth;
Expand All @@ -91,9 +143,7 @@ angular.module('avRegistration')
// Only try to renew token when it's older than 50% of
// the expiration time
var now = new Date();
var secsDiff = getSecondsDifference(authmethod.lastAuthDate, now);
var halfLife = ConfigService.authTokenExpirationSeconds * 0.5;
if (secsDiff <= halfLife) {
if (!hasPassedHalfLifeExpiry(now.getTime(), isAdmin)) {
return;
}
authmethod.lastAuthDate = now;
Expand Down Expand Up @@ -775,18 +825,26 @@ angular.module('avRegistration')
return $http.post(url, data);
};

var lastRefreshMs = 0;
authmethod.refreshAuthToken = function(autheventid) {
var deferred = $q.defer();
var jnow = Date.now();
if (jnow - lastRefreshMs < 1000) {
deferred.reject("ongoing refresh");
return deferred.promise;
} else {
lastRefreshMs = jnow;
}
var postfix = "_authevent_" + autheventid;

// ping daemon is not active for normal users
if (!authmethod.admin) {

/*if (!authmethod.admin) {
var hasGracefulPeriod = window.sessionStorage.getItem('hasGracefulPeriod');
if (hasGracefulPeriod === "true") {
deferred.reject("not an admin");
return deferred.promise;
}
}
}*/
// if document is hidden, then do not update the cookie, and redirect
// to admin logout if cookie expired
if (document.visibilityState === 'hidden') {
Expand All @@ -801,51 +859,56 @@ angular.module('avRegistration')
return authmethod.ping(autheventid)
.then(function(response) {
var options = {};
if (ConfigService.authTokenExpirationSeconds) {
options.expires = new Date(now + 1000 * ConfigService.authTokenExpirationSeconds);
var authToken = response.data['auth-token'];
if (authToken) {
var decodedToken = authmethod.decodeToken(authToken);
options.expires = new Date(now + 1000 * decodedToken.expiry_secs_diffs);
// update cookies expiration
$cookies.put(
"auth" + postfix,
response.data['auth-token'],
options
);
$cookies.put(
"isAdmin" + postfix,
$cookies.get("isAdmin" + postfix),
options
);
$cookies.put(
"userid" + postfix,
$cookies.get("userid" + postfix),
options
);
$cookies.put(
"userid" + postfix,
$cookies.get("userid" + postfix),
options
);
$cookies.put(
"user" + postfix,
$cookies.get("user" + postfix),
options
);
authmethod.setAuth(
$cookies.get("auth" + postfix),
$cookies.get("isAdmin" + postfix),
autheventid
);
}
// update cookies expiration
$cookies.put(
"auth" + postfix,
response.data['auth-token'],
options
);
$cookies.put(
"isAdmin" + postfix,
$cookies.get("isAdmin" + postfix),
options
);
$cookies.put(
"userid" + postfix,
$cookies.get("userid" + postfix),
options
);
$cookies.put(
"userid" + postfix,
$cookies.get("userid" + postfix),
options
);
$cookies.put(
"user" + postfix,
$cookies.get("user" + postfix),
options
);
authmethod.setAuth(
$cookies.get("auth" + postfix),
$cookies.get("isAdmin" + postfix),
autheventid
);

// if it's an election with no children elections
if (angular.isDefined(response.data['vote-permission-token']))
{
var accessToken = response.data['vote-permission-token'];
var decodedAccessToken = authmethod.decodeToken(accessToken);
$window.sessionStorage.setItem(
"vote_permission_tokens",
JSON.stringify([{
electionId: autheventid,
token: response.data['vote-permission-token'],
isFirst: true,
sessionStartedAtMs: sessionStartedAtMs
sessionStartedAtMs: sessionStartedAtMs,
sessionEndsAtMs: sessionStartedAtMs + 1000 * decodedAccessToken.expiry_secs_diff
}])
);
$window.sessionStorage.setItem(
Expand All @@ -860,6 +923,8 @@ angular.module('avRegistration')
var tokens = _
.chain(response.data['vote-children-info'])
.map(function (child, index) {
var accessToken = child['vote-permission-token'];
var decodedAccessToken = accessToken && authmethod.decodeToken(accessToken) || null;
return {
electionId: child['auth-event-id'],
token: child['vote-permission-token'] || null,
Expand All @@ -868,7 +933,8 @@ angular.module('avRegistration')
numSuccessfulLoginsAllowed: child['num-successful-logins-allowed'],
numSuccessfulLogins: child['num-successful-logins'],
isFirst: index === 0,
sessionStartedAtMs: sessionStartedAtMs
sessionStartedAtMs: sessionStartedAtMs,
sessionEndsAtMs: sessionStartedAtMs + 1000 * (decodedAccessToken && decodedAccessToken.expiry_secs_diff || null)
};
})
.value();
Expand Down
26 changes: 16 additions & 10 deletions avRegistration/login-directive/login-directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -679,17 +679,17 @@ angular.module('avRegistration')
if (response.data.status === "ok") {
var postfix = "_authevent_" + autheventid;
var options = {};
if (ConfigService.authTokenExpirationSeconds) {
options.expires = new Date(
Date.now() + 1000 * ConfigService.authTokenExpirationSeconds
);
}
var authToken = response.data['auth-token'];
var decodedToken = Authmethod.decodeToken(authToken);
options.expires = new Date(
sessionStartedAtMs + 1000 * decodedToken.expiry_secs_diff
);
$cookies.put("authevent_" + autheventid, autheventid, options);
$cookies.put("userid" + postfix, response.data.username, options);
$cookies.put("user" + postfix, scope.email || response.data.username || response.data.email, options);
$cookies.put("auth" + postfix, response.data['auth-token'], options);
$cookies.put("auth" + postfix, authToken, options);
$cookies.put("isAdmin" + postfix, scope.isAdmin, options);
Authmethod.setAuth($cookies.get("auth" + postfix), scope.isAdmin, autheventid);
Authmethod.setAuth(authToken, scope.isAdmin, autheventid);
if (scope.isAdmin)
{
Authmethod.getUserInfo()
Expand All @@ -716,13 +716,16 @@ angular.module('avRegistration')
// if it's an election with no children elections
else if (angular.isDefined(response.data['vote-permission-token']))
{
var accessToken = response.data['vote-permission-token'];
var decodedAccessToken = Authmethod.decodeToken(accessToken);
$window.sessionStorage.setItem(
"vote_permission_tokens",
JSON.stringify([{
electionId: autheventid,
token: response.data['vote-permission-token'],
isFirst: true,
sessionStartedAtMs: sessionStartedAtMs
sessionStartedAtMs: sessionStartedAtMs,
sessionEndsAtMs: sessionStartedAtMs + 1000 * decodedAccessToken.expiry_secs_diff
}])
);
$window.sessionStorage.setItem(
Expand All @@ -734,10 +737,12 @@ angular.module('avRegistration')
// if it's an election with children elections then show access to them
else if (angular.isDefined(response.data['vote-children-info']))
{
// assumes the iam response has the same children
// assumes the iam response has the same children
var tokens = _
.chain(response.data['vote-children-info'])
.map(function (child, index) {
var accessToken = child['vote-permission-token'];
var decodedAccessToken = accessToken && Authmethod.decodeToken(accessToken) || null;
return {
electionId: child['auth-event-id'],
token: child['vote-permission-token'] || null,
Expand All @@ -746,7 +751,8 @@ angular.module('avRegistration')
numSuccessfulLoginsAllowed: child['num-successful-logins-allowed'],
numSuccessfulLogins: child['num-successful-logins'],
isFirst: index === 0,
sessionStartedAtMs: sessionStartedAtMs
sessionStartedAtMs: sessionStartedAtMs,
sessionEndsAtMs: sessionStartedAtMs + 1000 * (decodedAccessToken && decodedAccessToken.expiry_secs_diff || null)
};
})
.value();
Expand Down
23 changes: 16 additions & 7 deletions avUi/common-header-directive/common-header-directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ angular

scope.showVersionsModal = ShowVersionsModalService;

function calculateCountdownPercent() {
function calculateCountdownPercent() {
var ratio = (scope.logoutTimeMs - Date.now())/(scope.logoutTimeMs - scope.countdownStartTimeMs);
return Math.min(100, Math.round(10000*ratio)/100) + '%';
}
Expand All @@ -67,6 +67,14 @@ angular

// helper function for enableLogoutCountdown()
function updateTimedown() {
if (scope.$parent.getSessionEndTime) {
scope.logoutTimeMs = scope.$parent.getSessionEndTime();
}

if (scope.$parent.getSessionStartTime) {
scope.countdownStartTimeMs = scope.$parent.getSessionStartTime(false);
}

scope.showCountdown = true;
var now = Date.now();
scope.countdownSecs = Math.round((scope.logoutTimeMs - now) / 1000);
Expand All @@ -77,12 +85,9 @@ angular
if (scope.countdownSecs <= 1) {
return;
}
var targetMins = Math.floor((scope.logoutTimeMs - now) / (60 * 1000));
var targetNextTime = scope.logoutTimeMs - targetMins * 60 * 1000;
var targetElapsedTime = targetNextTime - now;
setTimeout(
updateTimedown,
targetMins > 0? targetElapsedTime : 1000
1000
);
}

Expand Down Expand Up @@ -112,13 +117,17 @@ angular
scope.countdownSecs = 0;
scope.countdownMins = 0;

var initialTimeMs = scope.$parent.getSessionStartTime && scope.$parent.getSessionStartTime() || Date.now();
var initialTimeMs = scope.$parent.getSessionStartTime && scope.$parent.getSessionStartTime(true) || Date.now();
scope.elapsedCountdownMs = (
election.presentation.booth_log_out__countdown_seconds > 0?
election.presentation.booth_log_out__countdown_seconds :
ConfigService.authTokenExpirationSeconds
) * 1000;
scope.logoutTimeMs = initialTimeMs + ConfigService.authTokenExpirationSeconds * 1000;
if (scope.$parent.getSessionEndTime) {
scope.logoutTimeMs = scope.$parent.getSessionEndTime();
} else {
scope.logoutTimeMs = initialTimeMs + ConfigService.authTokenExpirationSeconds * 1000;
}
scope.countdownStartTimeMs = scope.logoutTimeMs - scope.elapsedCountdownMs;
scope.countdownPercent = calculateCountdownPercent();
updateProgressBar(scope.countdownPercent);
Expand Down
Loading

0 comments on commit a8a38a0

Please sign in to comment.