Skip to content

Commit

Permalink
[add-otp]
Browse files Browse the repository at this point in the history
Add subcommand with secret, user, digits, algo and period arguments
  • Loading branch information
karakays committed Jul 12, 2021
1 parent 9fa8adc commit 0e02dbc
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 156 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ clean-pyc:
find . -name '*.pyo' -exec rm -f {} +
find . -name __pycache__ -delete

bdist:
build:
$(PYTHON) setup.py bdist

sdist:
Expand Down
5 changes: 4 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ github:karakays
udemy:skarakayali
binance

# [add] add otp
# [rm] remove otp
- otp add --secret -- issuer --user --digits --algo --period
only issuer and secret mandatory

# [rm] rm by completion
- otp rm <Tab>

# [decode-qr-image]
* decode qr code
https://betterprogramming.pub/how-to-generate-and-decode-qr-codes-in-python-a933bce56fd0
Expand Down
21 changes: 19 additions & 2 deletions bin/otp
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
#!/usr/bin/env python
import argparse
import asyncio
import base64
import binascii
import logging

from otp import otp, TOKENS


def parse_secret(secret):
try:
return base64.b32decode(secret)
except binascii.Error:
parser.error(f"{secret} is not a valid secret")


parser = argparse.ArgumentParser()

parser.add_argument('-c', '--copy', action='store_true', help='copy code to clipboard')
Expand All @@ -16,8 +26,15 @@ subparsers = parser.add_subparsers(dest='command')
subparsers.required = False

subparsers.add_parser('ls')
subparsers.add_parser('add')
subparsers.add_parser('rm')
add_parser = subparsers.add_parser('add')
rm_parser = subparsers.add_parser('rm')

add_parser.add_argument('-i', '--issuer', action='store', required=True, help='account provider')
add_parser.add_argument('-s', '--secret', action='store', type=parse_secret, required=True, help='base32 encoded secret key')
add_parser.add_argument('-u', '--user', action='store', help='user identifier')
add_parser.add_argument('-d', '--digits', action='store', type=int, choices=(6, 7, 8), help='length of passcode')
add_parser.add_argument('-a', '--algo', action='store', choices=(['SHA-1']), help='algorithm')
add_parser.add_argument('-p', '--period', action='store', help='period passcode valid for, in seconds')

args = parser.parse_args()

Expand Down
16 changes: 9 additions & 7 deletions otp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@
from . import qr_code
from . import progress
from . import token
from . import configure
from .token import InvalidTokenUriError, Token

os.makedirs(os.environ["HOME"] + '/.otp/', 0o774, exist_ok=True)

CONFIG_PATH = os.environ["HOME"] + '/.otp/' + 'config'

TOKENS = {}
Expand All @@ -51,7 +52,7 @@ def parse_otp_uri(uri):

kwargs = {'secret': secret, 'type': uri.netloc}
label = uri.path[1:]
issuer, user = label.split(':')
issuer, user = (label.split(':')) if ':' in label else (label, None)
kwargs['issuer'] = issuer
kwargs['user'] = user

Expand All @@ -66,9 +67,10 @@ def parse_otp_uri(uri):
for index, line in enumerate(sanitized, start=1):
uri = urlparse(unquote(line))
token_args = parse_otp_uri(uri)
secret = base64.b32decode(token_args['secret'])
period = int(token_args['period'])
digits = int(token_args['digits'])
t = Token(index, None, issuer=token_args['issuer'], user=token_args['user'], secret=secret,
period=period, algorithm=token_args['algorithm'], digits=digits)
token_args['index'] = index
issuer = token_args.pop('issuer')
secret = base64.b32decode(token_args.pop('secret'))
token_args.update(period=int(token_args['period']))
token_args.update(digits=int(token_args['digits']))
t = Token(issuer, secret, **token_args)
TOKENS[index] = t
83 changes: 0 additions & 83 deletions otp/configure.py

This file was deleted.

2 changes: 1 addition & 1 deletion otp/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
def create_uri(token):
"""
otpauh://TYPE/LABEL?PARAMS
where LABEL of form issuer:account
where LABEL is of form issuer:account
"""
query = {'secret': base64.b32encode(token.secret), 'period': token.period,
'algorithm': token.algorithm, 'digits': token.digits}
Expand Down
65 changes: 12 additions & 53 deletions otp/otp.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import asyncio
import base64
import logging

import pyperclip

from otp import configure, TOKENS
from otp import TOKENS, CONFIG_PATH
from otp import core
from otp.token import Token, TokenType, InvalidTokenUriError

Expand All @@ -34,11 +33,11 @@

async def run(args):
try:
args_dict = {k: v for k, v in vars(args).items() if v}
argsd = {k: v for k, v in vars(args).items() if v}

logging.basicConfig(level=args_dict.pop('loglevel'))
logging.basicConfig(level=argsd.pop('loglevel'))

cmd = args_dict.get('command')
cmd = argsd.get('command')

logger.debug('Command=%s is being called %s', cmd)
if cmd is None:
Expand All @@ -56,10 +55,14 @@ async def run(args):
elif cmd == 'ls':
for t in TOKENS.values():
print(f'{t.index}: {t}')
elif cmd == 'config':
configure.configure()
else:
assert cmd, 'Unknown command {}'.format(cmd)
elif cmd == 'add':
import io, os
issuer = argsd.pop('issuer')
secret = argsd.pop('secret')
token = Token(issuer, secret, **argsd)
uri = core.create_uri(token)
with io.open(CONFIG_PATH, 'a+', encoding='UTF-8', newline=None) as f:
f.write(uri + os.linesep)
except InvalidTokenUriError:
logger.error('Invalid token', exc_info=1)
except KeyboardInterrupt:
Expand All @@ -68,49 +71,5 @@ async def run(args):
logger.error('🤕 Oh no, something\'s wrong here', exc_info=1)


def get_otp_by_secret(secret, **kwargs):
if not secret:
raise InvalidTokenUriError('Secret cannot be null')

try:
secret = base64.b32decode(secret)
except binascii.Error as e:
raise InvalidTokenUriError('Invalid secret', secret) from e

token_type = TokenType.fromValue(kwargs.get('type', 'hotp'))

issuer = kwargs.get('issuer')
user = kwargs.get('user')

period = kwargs.get('period')
try:
period = 30 if not period else int(period)
except ValueError as e:
raise InvalidTokenUriError('Invalid period', period) from e

digits = kwargs.get('digits')
try:
digits = 6 if not digits else int(digits)
if digits not in (6, 7, 8):
raise ValueError
except ValueError as e:
raise InvalidTokenUriError('Invalid digits', digits) from e

algorithm = kwargs.get('algorithm')
try:
algorithm = 'SHA1' if not algorithm else algorithm
if algorithm not in ('SHA1', 'SHA256', 'SHA512'):
raise ValueError
except ValueError as e:
raise InvalidTokenUriError('Invalid encryption algorithm',
algorithm) from e

return Token(t_type=token_type, issuer=issuer, user=user, secret=secret, period=period, algorithm=algorithm, digits=digits)


def add_token():
pass


if __name__ == '__main__':
run(None)
15 changes: 7 additions & 8 deletions otp/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,15 @@ def __str__(self):


class Token:
def __init__(self, index, t_type, secret, issuer='undefined', user='undefined',
period=30, algorithm='SHA-256', digits=6):
self.index = index
self.type = t_type
def __init__(self, issuer, secret, **kwargs):
self.issuer = issuer
self.user = user
self.secret = secret
self.period = period
self.algorithm = algorithm
self.digits = digits
self.index = kwargs.get('index')
self.type = kwargs.get('type', 'totp')
self.user = kwargs.get('user')
self.period = kwargs.get('period', 30)
self.algorithm = kwargs.get('algorithm', 'SHA-1')
self.digits = kwargs.get('digits', 6)

def toUri(self):
"""
Expand Down

0 comments on commit 0e02dbc

Please sign in to comment.