Skip to content

Commit

Permalink
Add a passlib-totp command
Browse files Browse the repository at this point in the history
Adds a passlib-totp command for generating one-time passwords from a
TOTP shared secret.
  • Loading branch information
fredrikhl committed Jun 2, 2023
1 parent db256f5 commit e68fe7f
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 0 deletions.
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ scrypt =
[options.entry_points]
console_scripts =
passlib-mkpasswd = passlib_cli.__main__:main
passlib-totp = passlib_cli.totp:main
passlib-autocomplete = passlib_cli.complete:main

[aliases]
Expand Down
97 changes: 97 additions & 0 deletions src/passlib_cli/totp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env python
# encoding: utf-8
""" TOTP CLI utils. """

from __future__ import (
absolute_import,
division,
print_function,
unicode_literals
)

import argparse
import logging
import string
import sys
import textwrap
import time

from passlib import totp

from . import cli_utils

logger = logging.getLogger(__name__)

BASE32_HINT = set(string.ascii_uppercase + '234567')


def get_totp(secret):
if secret.startswith('otpauth://'):
return totp.TOTP.from_uri(secret)
is_base32_like = not (set(secret) - BASE32_HINT)
try:
int(secret, 16)
is_hex_like = True
except ValueError:
is_hex_like = False

if is_base32_like:
return totp.TOTP(key=secret, format='base32')
elif is_hex_like:
return totp.TOTP(key=secret, format='hex')

raise ValueError('invalid secret')


def get_token(t):
return t.generate()


parser = argparse.ArgumentParser(
description="Generate TOTP codes using passlib",
)
parser.add_argument(
'--live',
action='store_true',
help=textwrap.dedent(
"""
Keep generating one-time passwords
"""
),
)
cli_utils.add_version_arg(parser)
cli_utils.add_verbosity_mutex(parser)


def main(inargs=None):
# print(__name__)
# print(__package__)
# print(__file__)
# print(os.path.basename(__file__))
args = parser.parse_args(inargs)
cli_utils.setup_logging(args.verbosity)

secret = sys.stdin.readline().rstrip()
generator = get_totp(secret)

def needs_wait(token):
return token.expire_time - time.time()

while True:
token = get_token(generator)
print(token.token)

if args.live:
while (tleft := needs_wait(token)) > 0:
logger.debug("time left: %d", tleft)
# sleep for one second at a time to catch e.g.
# KeyboardInterrupts
time.sleep(1)
continue
else:
break


if __name__ == '__main__':
parser.prog = 'python -m ' + __spec__.name
main()

0 comments on commit e68fe7f

Please sign in to comment.