Skip to content

Commit

Permalink
switch to an updated OTP lib and add support for custom OTP digits an…
Browse files Browse the repository at this point in the history
…d period values

Signed-off-by: binsky <[email protected]>
  • Loading branch information
binsky08 committed Aug 27, 2022
1 parent a73959e commit 54d1171
Show file tree
Hide file tree
Showing 11 changed files with 2,040 additions and 74 deletions.
2 changes: 2 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ module.exports = function (grunt) {
'js/vendor/download.js',
'js/vendor/ui-sortable/sortable.js', 'js/lib/promise.js',
'js/lib/crypto_wrap.js',
'js/lib/otpauth.umd.js',
'js/app/app.js',
'js/app/filters/*.js',
'js/app/services/*.js',
Expand Down Expand Up @@ -269,6 +270,7 @@ module.exports = function (grunt) {
'js/vendor/papa-parse/papaparse.min.js',
'js/lib/promise.js',
'js/lib/crypto_wrap.js',
'js/lib/otpauth.umd.js',
'js/app/app.js',
'js/app/filters/*.js',
'js/app/services/*.js',
Expand Down
2 changes: 2 additions & 0 deletions controller/translationcontroller.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ public function getLanguageStrings() {
'current.qr' => $this->trans->t('Current OTP settings'),
'issuer' => $this->trans->t('Issuer'),
'secret' => $this->trans->t('Secret'),
'digits' => $this->trans->t('Digits'),
'period' => $this->trans->t('Period'),


// templates/views/partials/edit_credential/password.html
Expand Down
5 changes: 4 additions & 1 deletion js/app/controllers/edit_credential.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,10 @@
label: decodeURIComponent(label),
qr_uri: QRCode,
issuer: uri.searchParams.get('issuer'),
secret: uri.searchParams.get('secret')
secret: uri.searchParams.get('secret'),
algorithm: uri.searchParams.get('algorithm') ? uri.searchParams.get('algorithm') : "SHA1",
period: uri.searchParams.get('period') ? parseInt(uri.searchParams.get('period')) : 30,
digits: uri.searchParams.get('digits') ? parseInt(uri.searchParams.get('digits')) : 6,
};
$scope.$digest();
};
Expand Down
99 changes: 38 additions & 61 deletions js/app/directives/otp.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,94 +30,71 @@
* # passwordGen
*/
angular.module('passmanApp')
.directive('otpGenerator', ['$compile', '$timeout',
function ($compile, $timeout) {
function dec2hex (s) {
return (s < 15.5 ? '0' : '') + Math.round(s).toString(16);
}

function hex2dec (s) {
return parseInt(s, 16);
}

function base32tohex (base32) {
if (!base32) {
return;
}
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = "";
var hex = "";
var i;
for (i = 0; i < base32.length; i++) {
var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
bits += leftpad(val.toString(2), 5, '0');
}

for (i = 0; i + 4 <= bits.length; i += 4) {
var chunk = bits.slice(i, i + 4);
hex = hex + parseInt(chunk, 2).toString(16);
}
return hex.length % 2 ? hex + "0" : hex;

}
.directive('otpGenerator', ['$compile', '$interval',
function ($compile, $interval) {
function mergeDefaultOTPConfig(otp) {
const defaults = {
algorithm: "SHA1",
period: 30,
digits: 6,
};

function leftpad (str, len, pad) {
if (len + 1 >= str.length) {
str = Array(len + 1 - str.length).join(pad) + str;
for (const key in defaults) {
if (otp[key] === undefined || otp[key] == null) {
otp[key] = defaults[key];
}
}
return str;
}

return {
restrict: 'A',
template: '<span class="otp_generator"><span credential-field value="otp" secret="\'true\'"></span> <span ng-bind="timeleft"></span></span>',
template: '<span class="otp_generator"><span credential-field value="token" secret="\'true\'"></span> <span ng-bind="timeleft"></span></span>',
transclude: false,
scope: {
secret: '='
otp: '='
},
replace: true,
link: function (scope) {
scope.otp = null;
scope.token = null;
scope.timeleft = null;
scope.timer = null;
var updateOtp = function () {
if (!scope.secret) {
if (!scope.otp || !scope.otp.secret || scope.otp.secret === "") {
return;
}
var key = base32tohex(scope.secret);
var epoch = Math.round(new Date().getTime() / 1000.0);
var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
/** global: jsSHA */
var hmacObj = new jsSHA(time, 'HEX');
var hmac = hmacObj.getHMAC(key, 'HEX', 'SHA-1', "HEX");
var offset = hex2dec(hmac.substring(hmac.length - 1));
var otp = (hex2dec(hmac.slice(offset * 2, offset * 2 + 8)) & hex2dec('7fffffff')) + '';
otp = (otp).slice(-6);
scope.otp = otp;

if (scope.otp.secret.includes(' ')) {
scope.otp.secret = scope.otp.secret.replaceAll(' ', '');
}
mergeDefaultOTPConfig(scope.otp);
var totp = new OTPAuth.TOTP({
issuer: scope.otp.issuer,
label: scope.otp.label,
algorithm: scope.otp.algorithm,
digits: scope.otp.digits,
period: scope.otp.period,
secret: scope.otp.secret
});
scope.token = totp.generate();
};

var timer = function () {
var epoch = Math.round(new Date().getTime() / 1000.0);
var countDown = 30 - (epoch % 30);
if (epoch % 30 === 0) updateOtp();
scope.timeleft = countDown;
scope.timer = $timeout(timer, 1000);

if (scope.otp) {
var epoch = Math.round(new Date().getTime() / 1000.0);
scope.timeleft = scope.otp.period - (epoch % scope.otp.period);
if (epoch % scope.otp.period === 1) updateOtp();
}
};
scope.$watch("secret", function (n) {
scope.$watch("otp", function (n) {
if (n) {
$timeout.cancel(scope.timer);
$interval.cancel(scope.timer);
updateOtp();
timer();
} else {
$timeout.cancel(scope.timer);
scope.timer = $interval(timer, 1000);
}
}, true);
scope.$on(
"$destroy",
function () {
$timeout.cancel(scope.timer);
$interval.cancel(scope.timer);
}
);
}
Expand Down
Loading

0 comments on commit 54d1171

Please sign in to comment.