PyOTP 2.2.8__py2.py3-none-any.whl → 2.4.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyotp/__init__.py CHANGED
@@ -1,23 +1,102 @@
1
- from __future__ import (absolute_import, division,
2
- print_function, unicode_literals)
1
+ from typing import Any, Dict, Sequence
3
2
 
4
- from pyotp.hotp import HOTP # noqa
5
- from pyotp.otp import OTP # noqa
6
- from pyotp.totp import TOTP # noqa
7
- from . import utils # noqa
3
+ import hashlib
4
+ from re import split
5
+ from urllib.parse import unquote, urlparse, parse_qsl
8
6
 
9
- def random_base32(length=16, random=None,
10
- chars=list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')):
7
+ from .compat import random
8
+ from .hotp import HOTP as HOTP
9
+ from .otp import OTP as OTP
10
+ from .totp import TOTP as TOTP
11
11
 
12
- # Use secrets module if available (Python version >= 3.6) per PEP 506
13
- try:
14
- import secrets
15
- random = secrets.SystemRandom()
16
- except ImportError:
17
- import random as _random
18
- random = _random.SystemRandom()
12
+
13
+ def random_base32(
14
+ length: int = 16,
15
+ chars: Sequence[str] = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')) -> str:
16
+ if length < 16:
17
+ raise Exception("Secrets should be at least 128 bits")
19
18
 
20
19
  return ''.join(
21
20
  random.choice(chars)
22
21
  for _ in range(length)
23
22
  )
23
+
24
+
25
+ def random_hex(
26
+ length: int = 32,
27
+ chars: Sequence[str] = list('ABCDEF0123456789')) -> str:
28
+ if length < 32:
29
+ raise Exception("Secrets should be at least 128 bits")
30
+ return random_base32(length=length, chars=chars)
31
+
32
+
33
+ def parse_uri(uri: str) -> OTP:
34
+ """
35
+ Parses the provisioning URI for the OTP; works for either TOTP or HOTP.
36
+
37
+ See also:
38
+ https://github.com/google/google-authenticator/wiki/Key-Uri-Format
39
+
40
+ :param uri: the hotp/totp URI to parse
41
+ :returns: OTP object
42
+ """
43
+
44
+ # Secret (to be filled in later)
45
+ secret = None
46
+
47
+ # Data we'll parse to the correct constructor
48
+ otp_data = {} # type: Dict[str, Any]
49
+
50
+ # Parse with URLlib
51
+ parsed_uri = urlparse(unquote(uri))
52
+
53
+ if parsed_uri.scheme != 'otpauth':
54
+ raise ValueError('Not an otpauth URI')
55
+
56
+ # Parse issuer/accountname info
57
+ accountinfo_parts = split(':|%3A', parsed_uri.path[1:], maxsplit=1)
58
+ if len(accountinfo_parts) == 1:
59
+ otp_data['name'] = accountinfo_parts[0]
60
+ else:
61
+ otp_data['issuer'] = accountinfo_parts[0]
62
+ otp_data['name'] = accountinfo_parts[1]
63
+
64
+ # Parse values
65
+ for key, value in parse_qsl(parsed_uri.query):
66
+ if key == 'secret':
67
+ secret = value
68
+ elif key == 'issuer':
69
+ if 'issuer' in otp_data and otp_data['issuer'] is not None and otp_data['issuer'] != value:
70
+ raise ValueError('If issuer is specified in both label and parameters, it should be equal.')
71
+ otp_data['issuer'] = value
72
+ elif key == 'algorithm':
73
+ if value == 'SHA1':
74
+ otp_data['digest'] = hashlib.sha1
75
+ elif value == 'SHA256':
76
+ otp_data['digest'] = hashlib.sha256
77
+ elif value == 'SHA512':
78
+ otp_data['digest'] = hashlib.sha512
79
+ else:
80
+ raise ValueError('Invalid value for algorithm, must be SHA1, SHA256 or SHA512')
81
+ elif key == 'digits':
82
+ digits = int(value)
83
+ if digits not in [6, 8]:
84
+ raise ValueError('Digits may only be 6 or 8')
85
+ otp_data['digits'] = digits
86
+ elif key == 'period':
87
+ otp_data['interval'] = value
88
+ elif key == 'counter':
89
+ otp_data['initial_count'] = value
90
+ else:
91
+ raise ValueError('{} is not a valid parameter'.format(key))
92
+
93
+ if not secret:
94
+ raise ValueError('No secret found in URI')
95
+
96
+ # Create objects
97
+ if parsed_uri.netloc == 'totp':
98
+ return TOTP(secret, **otp_data)
99
+ elif parsed_uri.netloc == 'hotp':
100
+ return HOTP(secret, **otp_data)
101
+
102
+ raise ValueError('Not a supported OTP type')
pyotp/compat.py CHANGED
@@ -1,10 +1,7 @@
1
- from __future__ import absolute_import, division, print_function, unicode_literals
1
+ # Use secrets module if available (Python version >= 3.6) per PEP 506
2
+ try:
3
+ from secrets import SystemRandom # type: ignore
4
+ except ImportError:
5
+ from random import SystemRandom
2
6
 
3
- import sys
4
-
5
- USING_PYTHON2 = True if sys.version_info < (3, 0) else False
6
-
7
- if USING_PYTHON2:
8
- str = unicode # noqa
9
- else:
10
- str = str
7
+ random = SystemRandom()
pyotp/hotp.py CHANGED
@@ -1,36 +1,43 @@
1
- from __future__ import absolute_import, division, print_function, unicode_literals
1
+ from typing import Any, Optional
2
2
 
3
3
  from . import utils
4
4
  from .otp import OTP
5
- from .compat import str
5
+
6
6
 
7
7
  class HOTP(OTP):
8
8
  """
9
9
  Handler for HMAC-based OTP counters.
10
10
  """
11
- def at(self, count):
11
+ def __init__(self, *args: Any, initial_count: int = 0, **kwargs: Any) -> None:
12
+ """
13
+ :param initial_count: starting HMAC counter value, defaults to 0
14
+ """
15
+ self.initial_count = initial_count
16
+ super(HOTP, self).__init__(*args, **kwargs)
17
+
18
+ def at(self, count: int) -> str:
12
19
  """
13
20
  Generates the OTP for the given count.
14
21
 
15
22
  :param count: the OTP HMAC counter
16
- :type count: int
17
23
  :returns: OTP
18
- :rtype: str
19
24
  """
20
25
  return self.generate_otp(count)
21
26
 
22
- def verify(self, otp, counter):
27
+ def verify(self, otp: str, counter: int) -> bool:
23
28
  """
24
29
  Verifies the OTP passed in against the current counter OTP.
25
30
 
26
31
  :param otp: the OTP to check against
27
- :type otp: str
28
- :param count: the OTP HMAC counter
29
- :type count: int
32
+ :param counter: the OTP HMAC counter
30
33
  """
31
34
  return utils.strings_equal(str(otp), str(self.at(counter)))
32
35
 
33
- def provisioning_uri(self, name, initial_count=0, issuer_name=None):
36
+ def provisioning_uri(
37
+ self,
38
+ name: Optional[str] = None,
39
+ initial_count: Optional[int] = None,
40
+ issuer_name: Optional[str] = None) -> str:
34
41
  """
35
42
  Returns the provisioning URI for the OTP. This can then be
36
43
  encoded in a QR Code and used to provision an OTP app like
@@ -40,19 +47,16 @@ class HOTP(OTP):
40
47
  https://github.com/google/google-authenticator/wiki/Key-Uri-Format
41
48
 
42
49
  :param name: name of the user account
43
- :type name: str
44
50
  :param initial_count: starting HMAC counter value, defaults to 0
45
- :type initial_count: int
46
51
  :param issuer_name: the name of the OTP issuer; this will be the
47
52
  organization title of the OTP entry in Authenticator
48
53
  :returns: provisioning URI
49
- :rtype: str
50
54
  """
51
55
  return utils.build_uri(
52
56
  self.secret,
53
- name,
54
- initial_count=initial_count,
55
- issuer_name=issuer_name,
57
+ name=name if name else self.name,
58
+ initial_count=initial_count if initial_count else self.initial_count,
59
+ issuer=issuer_name if issuer_name else self.issuer,
56
60
  algorithm=self.digest().name,
57
61
  digits=self.digits
58
62
  )
pyotp/otp.py CHANGED
@@ -1,32 +1,39 @@
1
- from __future__ import absolute_import, division, print_function, unicode_literals
1
+ from typing import Any, Optional
2
2
 
3
3
  import base64
4
4
  import hashlib
5
5
  import hmac
6
- from .compat import str
6
+
7
7
 
8
8
  class OTP(object):
9
9
  """
10
10
  Base class for OTP handlers.
11
11
  """
12
- def __init__(self, s, digits=6, digest=hashlib.sha1):
12
+ def __init__(
13
+ self,
14
+ s: str,
15
+ digits: int = 6,
16
+ digest: Any = hashlib.sha1,
17
+ name: Optional[str] = None,
18
+ issuer: Optional[str] = None
19
+ ) -> None:
13
20
  """
14
21
  :param s: secret in base32 format
15
- :type s: str
16
22
  :param digits: number of integers in the OTP. Some apps expect this to be 6 digits, others support more.
17
- :type digits: int
18
23
  :param digest: digest function to use in the HMAC (expected to be sha1)
19
- :type digest: callable
24
+ :param name: account name
25
+ :param issuer: issuer
20
26
  """
21
27
  self.digits = digits
22
28
  self.digest = digest
23
29
  self.secret = s
30
+ self.name = name or 'Secret'
31
+ self.issuer = issuer
24
32
 
25
- def generate_otp(self, input):
33
+ def generate_otp(self, input: int) -> str:
26
34
  """
27
35
  :param input: the HMAC counter value to use as the OTP input.
28
36
  Usually either the counter, or the computed integer based on the Unix timestamp
29
- :type input: int
30
37
  """
31
38
  if input < 0:
32
39
  raise ValueError('input must be positive integer')
@@ -43,14 +50,14 @@ class OTP(object):
43
50
 
44
51
  return str_code
45
52
 
46
- def byte_secret(self):
53
+ def byte_secret(self) -> bytes:
47
54
  missing_padding = len(self.secret) % 8
48
55
  if missing_padding != 0:
49
56
  self.secret += '=' * (8 - missing_padding)
50
57
  return base64.b32decode(self.secret, casefold=True)
51
58
 
52
59
  @staticmethod
53
- def int_to_bytestring(i, padding=8):
60
+ def int_to_bytestring(i: int, padding: int = 8) -> bytes:
54
61
  """
55
62
  Turns an integer to the OATH specified
56
63
  bytestring, which is fed to the HMAC
pyotp/py.typed ADDED
File without changes
pyotp/totp.py CHANGED
@@ -1,60 +1,57 @@
1
- from __future__ import absolute_import, division, print_function, unicode_literals
1
+ from typing import Any, Union, Optional
2
2
 
3
3
  import datetime
4
4
  import time
5
5
 
6
6
  from . import utils
7
7
  from .otp import OTP
8
- from .compat import str
8
+
9
9
 
10
10
  class TOTP(OTP):
11
11
  """
12
12
  Handler for time-based OTP counters.
13
13
  """
14
- def __init__(self, *args, **kwargs):
14
+ def __init__(self, *args: Any, interval: int = 30, **kwargs: Any) -> None:
15
15
  """
16
16
  :param interval: the time interval in seconds
17
17
  for OTP. This defaults to 30.
18
- :type interval: int
19
18
  """
20
- self.interval = kwargs.pop('interval', 30)
19
+ self.interval = interval
21
20
  super(TOTP, self).__init__(*args, **kwargs)
22
21
 
23
- def at(self, for_time, counter_offset=0):
22
+ def at(self, for_time: Union[int, datetime.datetime], counter_offset: int = 0) -> str:
24
23
  """
25
24
  Accepts either a Unix timestamp integer or a datetime object.
26
25
 
26
+ To get the time until the next timecode change (seconds until the current OTP expires), use this instead::
27
+
28
+ totp = pyotp.TOTP(...)
29
+ time_remaining = totp.interval - datetime.datetime.now().timestamp() % totp.interval
30
+
27
31
  :param for_time: the time to generate an OTP for
28
- :type for_time: int or datetime
29
32
  :param counter_offset: the amount of ticks to add to the time counter
30
33
  :returns: OTP value
31
- :rtype: str
32
34
  """
33
35
  if not isinstance(for_time, datetime.datetime):
34
36
  for_time = datetime.datetime.fromtimestamp(int(for_time))
35
37
  return self.generate_otp(self.timecode(for_time) + counter_offset)
36
38
 
37
- def now(self):
39
+ def now(self) -> str:
38
40
  """
39
41
  Generate the current time OTP
40
42
 
41
43
  :returns: OTP value
42
- :rtype: str
43
44
  """
44
45
  return self.generate_otp(self.timecode(datetime.datetime.now()))
45
46
 
46
- def verify(self, otp, for_time=None, valid_window=0):
47
+ def verify(self, otp: str, for_time: Optional[datetime.datetime] = None, valid_window: int = 0) -> bool:
47
48
  """
48
49
  Verifies the OTP passed in against the current time OTP.
49
50
 
50
51
  :param otp: the OTP to check against
51
- :type otp: str
52
52
  :param for_time: Time to check OTP at (defaults to now)
53
- :type for_time: int or datetime
54
53
  :param valid_window: extends the validity to this many counter ticks before and after the current one
55
- :type valid_window: int
56
54
  :returns: True if verification succeeded, False otherwise
57
- :rtype: bool
58
55
  """
59
56
  if for_time is None:
60
57
  for_time = datetime.datetime.now()
@@ -67,7 +64,7 @@ class TOTP(OTP):
67
64
 
68
65
  return utils.strings_equal(str(otp), str(self.at(for_time)))
69
66
 
70
- def provisioning_uri(self, name, issuer_name=None):
67
+ def provisioning_uri(self, name: Optional[str] = None, issuer_name: Optional[str] = None) -> str:
71
68
  """
72
69
  Returns the provisioning URI for the OTP. This can then be
73
70
  encoded in a QR Code and used to provision an OTP app like
@@ -76,17 +73,12 @@ class TOTP(OTP):
76
73
  See also:
77
74
  https://github.com/google/google-authenticator/wiki/Key-Uri-Format
78
75
 
79
- :param name: name of the user account
80
- :type name: str
81
- :param issuer_name: the name of the OTP issuer; this will be the
82
- organization title of the OTP entry in Authenticator
83
- :returns: provisioning URI
84
- :rtype: str
85
76
  """
86
- return utils.build_uri(self.secret, name, issuer_name=issuer_name,
77
+ return utils.build_uri(self.secret, name if name else self.name,
78
+ issuer=issuer_name if issuer_name else self.issuer,
87
79
  algorithm=self.digest().name,
88
80
  digits=self.digits, period=self.interval)
89
81
 
90
- def timecode(self, for_time):
82
+ def timecode(self, for_time: datetime.datetime) -> int:
91
83
  i = time.mktime(for_time.timetuple())
92
84
  return int(i / self.interval)
pyotp/utils.py CHANGED
@@ -1,19 +1,18 @@
1
- from __future__ import absolute_import, division, print_function, unicode_literals
1
+ from typing import Dict, Optional, Union
2
2
 
3
3
  import unicodedata
4
- try:
5
- from itertools import izip_longest
6
- except ImportError:
7
- from itertools import zip_longest as izip_longest
8
-
9
- try:
10
- from urllib.parse import quote, urlencode
11
- except ImportError:
12
- from urllib import quote, urlencode
13
-
14
-
15
- def build_uri(secret, name, initial_count=None, issuer_name=None,
16
- algorithm=None, digits=None, period=None):
4
+ from hmac import compare_digest
5
+ from urllib.parse import quote, urlencode
6
+
7
+
8
+ def build_uri(
9
+ secret: str,
10
+ name: str,
11
+ initial_count: Optional[int] = None,
12
+ issuer: Optional[str] = None,
13
+ algorithm: Optional[str] = None,
14
+ digits: Optional[int] = None,
15
+ period: Optional[int] = None) -> str:
17
16
  """
18
17
  Returns the provisioning URI for the OTP; works for either TOTP or HOTP.
19
18
 
@@ -26,24 +25,16 @@ def build_uri(secret, name, initial_count=None, issuer_name=None,
26
25
  https://github.com/google/google-authenticator/wiki/Key-Uri-Format
27
26
 
28
27
  :param secret: the hotp/totp secret used to generate the URI
29
- :type secret: str
30
28
  :param name: name of the account
31
- :type name: str
32
29
  :param initial_count: starting counter value, defaults to None.
33
30
  If none, the OTP type will be assumed as TOTP.
34
- :type initial_count: int
35
- :param issuer_name: the name of the OTP issuer; this will be the
31
+ :param issuer: the name of the OTP issuer; this will be the
36
32
  organization title of the OTP entry in Authenticator
37
- :type issuer_name: str
38
33
  :param algorithm: the algorithm used in the OTP generation.
39
- :type algorithm: str
40
34
  :param digits: the length of the OTP generated code.
41
- :type digits: int
42
35
  :param period: the number of seconds the OTP generator is set to
43
36
  expire every code.
44
- :type period: int
45
37
  :returns: provisioning uri
46
- :rtype: str
47
38
  """
48
39
  # initial_count may be 0 as a valid param
49
40
  is_initial_count_present = (initial_count is not None)
@@ -56,17 +47,17 @@ def build_uri(secret, name, initial_count=None, issuer_name=None,
56
47
  otp_type = 'hotp' if is_initial_count_present else 'totp'
57
48
  base_uri = 'otpauth://{0}/{1}?{2}'
58
49
 
59
- url_args = {'secret': secret}
50
+ url_args = {'secret': secret} # type: Dict[str, Union[None, int, str]]
60
51
 
61
52
  label = quote(name)
62
- if issuer_name is not None:
63
- label = quote(issuer_name) + ':' + label
64
- url_args['issuer'] = issuer_name
53
+ if issuer is not None:
54
+ label = quote(issuer) + ':' + label
55
+ url_args['issuer'] = issuer
65
56
 
66
57
  if is_initial_count_present:
67
58
  url_args['counter'] = initial_count
68
59
  if is_algorithm_set:
69
- url_args['algorithm'] = algorithm.upper()
60
+ url_args['algorithm'] = algorithm.upper() # type: ignore
70
61
  if is_digits_set:
71
62
  url_args['digits'] = digits
72
63
  if is_period_set:
@@ -76,26 +67,7 @@ def build_uri(secret, name, initial_count=None, issuer_name=None,
76
67
  return uri
77
68
 
78
69
 
79
- def _compare_digest(s1, s2):
80
- differences = 0
81
- for c1, c2 in izip_longest(s1, s2):
82
- if c1 is None or c2 is None:
83
- differences = 1
84
- continue
85
- differences |= ord(c1) ^ ord(c2)
86
- return differences == 0
87
-
88
-
89
- try:
90
- # Python 3.3+ and 2.7.7+ include a timing-attack-resistant
91
- # comparison function, which is probably more reliable than ours.
92
- # Use it if available.
93
- from hmac import compare_digest
94
- except ImportError:
95
- compare_digest = _compare_digest
96
-
97
-
98
- def strings_equal(s1, s2):
70
+ def strings_equal(s1: str, s2: str) -> bool:
99
71
  """
100
72
  Timing-attack resistant string comparison.
101
73
 
@@ -106,4 +78,4 @@ def strings_equal(s1, s2):
106
78
  """
107
79
  s1 = unicodedata.normalize('NFKC', s1)
108
80
  s2 = unicodedata.normalize('NFKC', s2)
109
- return compare_digest(s1.encode(), s2.encode())
81
+ return compare_digest(s1.encode("utf-8"), s2.encode("utf-8"))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyotp
3
- Version: 2.2.8
3
+ Version: 2.4.0
4
4
  Summary: Python One Time Password Library
5
5
  Home-page: https://github.com/pyotp/pyotp
6
6
  Author: PyOTP contributors
@@ -13,7 +13,6 @@ Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Operating System :: MacOS :: MacOS X
14
14
  Classifier: Operating System :: POSIX
15
15
  Classifier: Programming Language :: Python
16
- Classifier: Programming Language :: Python :: 2.7
17
16
  Classifier: Programming Language :: Python :: 3.4
18
17
  Classifier: Programming Language :: Python :: 3.5
19
18
  Classifier: Programming Language :: Python :: 3.6
@@ -98,11 +97,15 @@ Counter-based OTPs
98
97
  hotp.verify('316439', 1401) # => True
99
98
  hotp.verify('316439', 1402) # => False
100
99
 
101
- Generating a base32 Secret Key
102
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
103
- ::
100
+ Generating a Secret Key
101
+ ~~~~~~~~~~~~~~~~~~~~~~~
102
+ A helper function is provided to generate a 16 character base32 secret, compatible with Google Authenticator and other OTP apps::
103
+
104
+ pyotp.random_base32()
104
105
 
105
- pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator and other OTP apps
106
+ Some applications want the secret key to be formatted as a hex-encoded string::
107
+
108
+ pyotp.random_hex() # returns a 32-character hex-encoded secret
106
109
 
107
110
  Google Authenticator Compatible
108
111
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -110,17 +113,27 @@ Google Authenticator Compatible
110
113
  PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes the
111
114
  ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
112
115
 
113
- pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice@google.com", issuer_name="Secure App")
116
+ pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name='alice@google.com', issuer_name='Secure App')
114
117
 
115
118
  >>> 'otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App'
116
119
 
117
- pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice@google.com", initial_count=0, issuer_name="Secure App")
120
+ pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name="alice@google.com", issuer_name="Secure App", initial_count=0)
118
121
 
119
122
  >>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
120
123
 
121
124
  This URL can then be rendered as a QR Code (for example, using https://github.com/neocotic/qrious) which can then be scanned
122
125
  and added to the users list of OTP credentials.
123
126
 
127
+ Parsing these URLs is also supported::
128
+
129
+ pyotp.parse_uri('otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App')
130
+
131
+ >>> <pyotp.totp.TOTP object at 0xFFFFFFFF>
132
+
133
+ pyotp.parse_uri('otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
134
+
135
+ >>> <pyotp.totp.HOTP object at 0xFFFFFFFF>
136
+
124
137
  Working example
125
138
  ~~~~~~~~~~~~~~~
126
139
 
@@ -137,10 +150,10 @@ Now run the following and compare the output::
137
150
  Links
138
151
  ~~~~~
139
152
 
140
- * `Project home page (GitHub) <https://github.com/pyotp/pyotp>`_
153
+ * `Project home page (GitHub) <https://github.com/pyauth/pyotp>`_
141
154
  * `Documentation (Read the Docs) <https://pyotp.readthedocs.io/en/latest/>`_
142
155
  * `Package distribution (PyPI) <https://pypi.python.org/pypi/pyotp>`_
143
- * `Change log <https://github.com/pyotp/pyotp/blob/master/Changes.rst>`_
156
+ * `Change log <https://github.com/pyauth/pyotp/blob/master/Changes.rst>`_
144
157
  * `RFC 4226: HOTP: An HMAC-Based One-Time Password <https://tools.ietf.org/html/rfc4226>`_
145
158
  * `RFC 6238: TOTP: Time-Based One-Time Password Algorithm <https://tools.ietf.org/html/rfc6238>`_
146
159
  * `ROTP <https://github.com/mdp/rotp>`_ - Original Ruby OTP library by `Mark Percival <https://github.com/mdp>`_
@@ -153,10 +166,10 @@ For new applications:
153
166
  * `WebAuthn <https://www.w3.org/TR/webauthn/>`_
154
167
  * `PyWARP <https://github.com/pyauth/pywarp>`_
155
168
 
156
- .. image:: https://img.shields.io/travis/pyotp/pyotp.svg
157
- :target: https://travis-ci.org/pyotp/pyotp
158
- .. image:: https://img.shields.io/codecov/c/github/pyotp/pyotp/master.svg
159
- :target: https://codecov.io/github/pyotp/pyotp?branch=master
169
+ .. image:: https://github.com/pyauth/pyotp/workflows/Python%20package/badge.svg
170
+ :target: https://github.com/pyauth/pyotp/actions
171
+ .. image:: https://img.shields.io/codecov/c/github/pyauth/pyotp/master.svg
172
+ :target: https://codecov.io/github/pyauth/pyotp?branch=master
160
173
  .. image:: https://img.shields.io/pypi/v/pyotp.svg
161
174
  :target: https://pypi.python.org/pypi/pyotp
162
175
  .. image:: https://img.shields.io/pypi/l/pyotp.svg
@@ -0,0 +1,12 @@
1
+ pyotp/__init__.py,sha256=z4ctyTv87_opGZTwvwlM8_EBaDaJsmGPxrKuipjxcg4,3223
2
+ pyotp/compat.py,sha256=i-pxcRWpShxkMTMucqPJdPJibYUaVtEhL9pyweKQ84k,209
3
+ pyotp/hotp.py,sha256=07e4wuWnCc9OJnQ8YX39vtsjSpSXPN_cue7S728Lmv0,2048
4
+ pyotp/otp.py,sha256=AHBnORBETYKnLqYzJUZnram6JddpNFvnHzedRpQbbyk,2479
5
+ pyotp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ pyotp/totp.py,sha256=uawrXOPiimcCys0WdYMD0vJqrIoe4l648Sv4R1mURT4,3140
7
+ pyotp/utils.py,sha256=6HBmC7ILxGOlFB6499Py_fufE4eyQvd4PU1gRngsydU,2836
8
+ pyotp-2.4.0.dist-info/LICENSE,sha256=f1C5lELlDYN3777_-tBjT7mgRr6HCXrzppnWNwsOG2M,1174
9
+ pyotp-2.4.0.dist-info/METADATA,sha256=DRJnqAlfj7tT58wV4Ci8wcU9mwO6XrJILgf1Sg4t-dg,8539
10
+ pyotp-2.4.0.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
11
+ pyotp-2.4.0.dist-info/top_level.txt,sha256=rFDgz6Sm4nbkYk6tlSRiMcJPyYdnY6LcZiAaYTxVc5k,6
12
+ pyotp-2.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.33.4)
2
+ Generator: bdist_wheel (0.34.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -1,11 +0,0 @@
1
- pyotp/__init__.py,sha256=hamm6NeVXq0-y6jlBlVnO7oct5n5EHWltinaBxge8xk,685
2
- pyotp/compat.py,sha256=QVb4buImn4PyP0qe5uLiXsO2-p4ItTGl01Vbt2dILNE,222
3
- pyotp/hotp.py,sha256=Jotukhsjpwpbx05iHHPYgH0HzpAFUvRAnWauQ_4N69M,1772
4
- pyotp/otp.py,sha256=QPyHxZ7fY7_zlrMVVveGQrCfn6gatpaHO3-ok9XwCaU,2344
5
- pyotp/totp.py,sha256=EHnv9irTDDt2cLUIxy3w_80QaqtduAM08Xq7rU2bIm0,3168
6
- pyotp/utils.py,sha256=smffRHfGU9fwpzmkLrKz6Z3xt07-PEJ2lf8mJ1Tvn7I,3513
7
- pyotp-2.2.8.dist-info/LICENSE,sha256=f1C5lELlDYN3777_-tBjT7mgRr6HCXrzppnWNwsOG2M,1174
8
- pyotp-2.2.8.dist-info/METADATA,sha256=OHVMnmqShG3BbmFJPG9C6bi50zdvLZ27XJHr_NOq148,8004
9
- pyotp-2.2.8.dist-info/WHEEL,sha256=h_aVn5OB2IERUjMbi2pucmR_zzWJtk303YXvhh60NJ8,110
10
- pyotp-2.2.8.dist-info/top_level.txt,sha256=rFDgz6Sm4nbkYk6tlSRiMcJPyYdnY6LcZiAaYTxVc5k,6
11
- pyotp-2.2.8.dist-info/RECORD,,
File without changes