~martijnbraam/numberstation

9c58b0d17aa3dff1ee52c2997fa5e8cae368a85e — Martijn Braam 2 years ago 8ecc39c fix-hotp
Implement the user interface for HOTP tokens
3 files changed, 46 insertions(+), 6 deletions(-)

M numberstation/otpurl.py
M numberstation/ui/style.css
M numberstation/window.py
M numberstation/otpurl.py => numberstation/otpurl.py +8 -1
@@ 20,6 20,7 @@ class OTPUrl:
        self.issuer = qs['issuer'][0] if 'issuer' in qs else None
        self.period = int(qs['period'][0]) if 'period' in qs else 30
        self.digits = int(qs['digits'][0]) if 'digits' in qs else 6
        self.initial_count = int(qs['initial_count'][0]) if 'initial_count' in qs else 0

        self.digest = hashlib.sha1
        if 'digest' in qs:


@@ 39,14 40,20 @@ class OTPUrl:
        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
        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

    def get_url(self):
        properties = {
            'secret': self.secret,
            'issuer': self.issuer,
            'period': self.period,
            'digits': self.digits
        }
        if self.type == 'totp':
            properties['period'] = self.period
        elif self.type == 'hotp':
            properties['initial_count'] = self.initial_count
        qs = urlencode(properties, doseq=True)
        url = urlunparse(['otpauth', self.type, '/' + self.label, '', qs, ''])
        return url

M numberstation/ui/style.css => numberstation/ui/style.css +6 -1
@@ 2,11 2,16 @@ label.token-label {

}

label.token-code {
label.token-code,
button.token-code {
    font-size: 2em;
    font-family: monospace;
}

button.token-code {
    margin-top: 7px;
}

label.errorbar {
    background: #c00;
    color: white;

M numberstation/window.py => numberstation/window.py +32 -4
@@ 137,13 137,22 @@ class NumberstationWindow:
            user_code = ' '.join([code[i:i + 3] for i in range(0, len(code), 3)])
        elif len(code) % 2 == 0:
            user_code = ' '.join([code[i:i + 2] for i in range(0, len(code), 2)])
        label.set_text(user_code)
        if token.type == 'totp':
            label.set_text(user_code)
        else:
            label.set_label(user_code)
        label.paste_code = paste_code
        progressbar.validity = int(validity)
        if validity:
            progressbar.validity = int(validity)
        else:
            if progressbar:
                progressbar.validity = None

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


@@ 219,9 228,15 @@ class NumberstationWindow:
            label.get_style_context().add_class('token-label')
            grid.attach(label, 0, 0, 1, 1)

            code = Gtk.Label("000000")
            if token.type == 'totp':
                code = Gtk.Label("000000")
            elif token.type == 'hotp':
                code = Gtk.Button("000000")
                code.set_hexpand(True)
                code.connect('clicked', self.on_hotp_click)

            code.get_style_context().add_class('token-code')
            code.token = token
            grid.attach(code, 0, 1, 1, 1)
            timer = Gtk.ProgressBar()
            timer.set_hexpand(True)


@@ 231,7 246,13 @@ class NumberstationWindow:

            timer.token = token
            timer.display = code
            timer.set_fraction(1.0 / token.period * timer.validity)
            if timer.validity is not None:
                timer.show()
                timer.set_no_show_all(False)
                timer.set_fraction(1.0 / token.period * timer.validity)
            else:
                timer.hide()
                timer.set_no_show_all(True)
            self.timers.append(timer)
            grid.attach(timer, 0, 2, 1, 1)
            eb.add(grid)


@@ 357,3 378,10 @@ class NumberstationWindow:
            with open(filename, 'w') as handle:
                handle.writelines(raw)
        dialog.destroy()

    def on_hotp_click(self, widget, *args):
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(widget.get_label().replace(' ', ''), -1)
        widget.token.initial_count += 1
        self.save_keyring()
        self.update_code_label(widget, None, widget.token)