~martijnbraam/numberstation

9a26d18f0d6ecdbb58fb1e28be803ffe6a5b7741 — Andrey Skvortsov 1 year, 1 month ago 078cf04
Update TOTP codes when they are expired

If system is suspended, then after resume TOTP are not updated until
'period' seconds are left in the wakeup state. During this time shown
TOTP codes are wrong, because they are calculated base on the
timestamp before system was suspended.
2 files changed, 25 insertions(+), 14 deletions(-)

M numberstation/otpurl.py
M numberstation/window.py
M numberstation/otpurl.py => numberstation/otpurl.py +14 -1
@@ 46,7 46,9 @@ class OTPUrl:
    def get_token(self):
        if self.type == 'totp':
            totp = pyotp.TOTP(self.secret, interval=self.period, digits=self.digits, digest=self.digest)
            return totp.now(), totp.interval - datetime.now().timestamp() % totp.interval
            base_timestamp = totp.interval * int(datetime.now().timestamp() / totp.interval)
            valid_until = datetime.fromtimestamp(base_timestamp + totp.interval)
            return totp.now(), valid_until
        elif self.type == 'hotp':
            hotp = pyotp.HOTP(self.secret, digits=self.digits, digest=self.digest, initial_count=self.initial_count)
            return hotp.at(0), None


@@ 54,6 56,17 @@ class OTPUrl:
            sys.stderr.write("Unknown key format '{}'\n".format(self.type))
            return None, None

    def get_validity(self):
        """Returns floating value in the range 0.0-1.0
        described how long generated code will be valid
        relative to update period
        """
        if self.type == 'totp':
            secs_left = self.period - datetime.now().timestamp() % self.period
            return 1.0 * secs_left / self.period
        else:
            return None

    def get_url(self):
        properties = {
            'secret': self.secret,

M numberstation/window.py => numberstation/window.py +11 -13
@@ 1,4 1,5 @@
from base64 import b32decode
from datetime import datetime
from collections import namedtuple

import keyring


@@ 128,7 129,7 @@ class NumberstationWindow:
            self.save_keyring()

    def update_code_label(self, label, progressbar, token):
        code, validity = token.get_token()
        code, valid_until = token.get_token()
        if code is None:
            return
        user_code = code


@@ 144,23 145,20 @@ class NumberstationWindow:
        else:
            label.set_label(user_code)
        label.paste_code = paste_code
        if validity:
            progressbar.validity = int(validity)
        if valid_until:
            progressbar.valid_until = valid_until
        else:
            if progressbar:
                progressbar.validity = None
                progressbar.valid_until = None

    def update_codes(self):
        GLib.timeout_add(1000, self.update_codes)
        for timer in self.timers:
            if timer.validity is None:
            if timer.valid_until is None:
                continue
            timer.validity -= 1
            if timer.validity < 1:
                timer.validity = int(timer.period)
            if timer.valid_until < datetime.now():
                self.update_code_label(timer.display, timer, timer.token)

            timer.set_fraction(1.0 / timer.period * timer.validity)
            timer.set_fraction(timer.token.get_validity())

    def on_long_press(self, gesture, x, y, *args):
        eb = gesture.eb


@@ 249,12 247,12 @@ class NumberstationWindow:

            timer.token = token
            timer.display = code
            if not hasattr(timer, 'validity'):
            if not hasattr(timer, 'valid_until'):
                continue
            if timer.validity is not None:
            if timer.valid_until is not None:
                timer.show()
                timer.set_no_show_all(False)
                timer.set_fraction(1.0 / token.period * timer.validity)
                timer.set_fraction(token.get_validity())
            else:
                timer.hide()
                timer.set_no_show_all(True)