Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pyotp for openSUSE:Factory 
checked in at 2023-12-15 21:48:30
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyotp (Old)
 and      /work/SRC/openSUSE:Factory/.python-pyotp.new.25432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pyotp"

Fri Dec 15 21:48:30 2023 rev:7 rq:1133203 version:2.9.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyotp/python-pyotp.changes        
2023-01-03 15:05:39.734613827 +0100
+++ /work/SRC/openSUSE:Factory/.python-pyotp.new.25432/python-pyotp.changes     
2023-12-15 21:48:43.521330722 +0100
@@ -1,0 +2,7 @@
+Thu Dec 14 21:30:56 UTC 2023 - Dirk Müller <dmuel...@suse.com>
+
+- update to 2.9.0:
+  * Add `parse_uri()` support for Steam TOTP (#153)
+  * Test and documentation improvements
+
+-------------------------------------------------------------------

Old:
----
  pyotp-2.8.0.tar.gz

New:
----
  pyotp-2.9.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pyotp.spec ++++++
--- /var/tmp/diff_new_pack.HieXdl/_old  2023-12-15 21:48:44.645372080 +0100
+++ /var/tmp/diff_new_pack.HieXdl/_new  2023-12-15 21:48:44.661372668 +0100
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-pyotp
-Version:        2.8.0
+Version:        2.9.0
 Release:        0
 Summary:        Python One Time Password Library
 License:        MIT

++++++ pyotp-2.8.0.tar.gz -> pyotp-2.9.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/PKG-INFO new/pyotp-2.9.0/PKG-INFO
--- old/pyotp-2.8.0/PKG-INFO    2022-12-14 04:54:21.049833300 +0100
+++ new/pyotp-2.9.0/PKG-INFO    2023-07-28 01:40:52.158836000 +0200
@@ -1,11 +1,15 @@
 Metadata-Version: 2.1
 Name: pyotp
-Version: 2.8.0
+Version: 2.9.0
 Summary: Python One Time Password Library
 Home-page: https://github.com/pyotp/pyotp
 Author: PyOTP contributors
 Author-email: kisl...@gmail.com
 License: MIT License
+Project-URL: Documentation, https://pyauth.github.io/pyotp
+Project-URL: Source Code, https://github.com/pyauth/pyotp
+Project-URL: Issue Tracker, https://github.com/pyauth/pyotp/issues
+Project-URL: Change Log, 
https://github.com/pyauth/pyotp/blob/master/Changes.rst
 Platform: MacOS X
 Platform: Posix
 Classifier: Intended Audience :: Developers
@@ -14,13 +18,14 @@
 Classifier: Operating System :: POSIX
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Requires-Python: >=3.7
+Provides-Extra: test
 License-File: LICENSE
 
 PyOTP - The Python One-Time Password Library
@@ -43,19 +48,21 @@
 
 - Ensure transport confidentiality by using HTTPS
 - Ensure HOTP/TOTP secret confidentiality by storing secrets in a controlled 
access database
-- Deny replay attacks by rejecting one-time passwords that have been used by 
the client (this requires storing the most 
-  recently authenticated timestamp, OTP, or hash of the OTP in your database, 
and rejecting the OTP when a match is seen)
+- Deny replay attacks by rejecting one-time passwords that have been used by 
the client (this requires storing the most
+  recently authenticated timestamp, OTP, or hash of the OTP in your database, 
and rejecting the OTP when a match is
+  seen)
 - Throttle (rate limit) brute-force attacks against your application's login 
functionality (see RFC 4226, section 7.3)
 - When implementing a "greenfield" application, consider supporting
   `FIDO U2F <https://en.wikipedia.org/wiki/Universal_2nd_Factor>`_/`WebAuthn 
<https://www.w3.org/TR/webauthn/>`_ in
   addition to HOTP/TOTP. U2F uses asymmetric cryptography to avoid using a 
shared secret design, which strengthens your
   MFA solution against server-side attacks. Hardware U2F also sequesters the 
client secret in a dedicated single-purpose
   device, which strengthens your clients against client-side attacks. And by 
automating scoping of credentials to
-  relying party IDs (application origin/domain names), U2F adds protection 
against phishing attacks. One implementation of
-  FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP 
<https://github.com/pyauth/pywarp>`_.
+  relying party IDs (application origin/domain names), U2F adds protection 
against phishing attacks. One implementation
+  of FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP 
<https://github.com/pyauth/pywarp>`_.
 
 We also recommend that implementers read the
-`OWASP Authentication Cheat Sheet 
<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_
 and
+`OWASP Authentication Cheat Sheet
+<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_
 and
 `NIST SP 800-63-3: Digital Authentication Guideline 
<https://pages.nist.gov/800-63-3/>`_ for a high level overview of
 authentication best practices.
 
@@ -64,8 +71,10 @@
 
 * OTPs involve a shared secret, stored both on the phone and the server
 * OTPs can be generated on a phone without internet connectivity
-* OTPs should always be used as a second factor of authentication (if your 
phone is lost, you account is still secured with a password)
-* Google Authenticator and other OTP client apps allow you to store multiple 
OTP secrets and provision those using a QR Code
+* OTPs should always be used as a second factor of authentication (if your 
phone is lost, you account is still secured
+  with a password)
+* Google Authenticator and other OTP client apps allow you to store multiple 
OTP secrets and provision those using a QR
+  Code
 
 Installation
 ------------
@@ -108,7 +117,8 @@
 
 Generating a Secret Key
 ~~~~~~~~~~~~~~~~~~~~~~~
-A helper function is provided to generate a 32-character base32 secret, 
compatible with Google Authenticator and other OTP apps::
+A helper function is provided to generate a 32-character base32 secret, 
compatible with Google Authenticator and other
+OTP apps::
 
     pyotp.random_base32()
 
@@ -119,8 +129,8 @@
 Google Authenticator Compatible
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-PyOTP works with the Google Authenticator iPhone and Android app, as well as 
other OTP apps like Authy. PyOTP includes the
-ability to generate provisioning URIs for use with the QR Code scanner built 
into these MFA client apps::
+PyOTP works with the Google Authenticator iPhone and Android app, as well as 
other OTP apps like Authy. PyOTP includes
+the ability to generate provisioning URIs for use with the QR Code scanner 
built into these MFA client apps::
 
     
pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name='al...@google.com', 
issuer_name='Secure App')
 
@@ -130,8 +140,8 @@
 
     >>> 
'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
 
-This URL can then be rendered as a QR Code (for example, using 
https://github.com/soldair/node-qrcode) which can then be scanned
-and added to the users list of OTP credentials.
+This URL can then be rendered as a QR Code (for example, using 
https://github.com/soldair/node-qrcode) which can then be
+scanned and added to the users list of OTP credentials.
 
 Parsing these URLs is also supported::
 
@@ -156,6 +166,13 @@
     totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
     print("Current OTP:", totp.now())
 
+Third-party contributions
+~~~~~~~~~~~~~~~~~~~~~~~~~
+The following third-party contributions are not described by a standard, not 
officially supported, and provided for
+reference only:
+
+* ``pyotp.contrib.Steam()``: An implementation of Steam TOTP. Uses the same 
API as `pyotp.TOTP()`.
+
 Links
 ~~~~~
 
@@ -175,6 +192,13 @@
 * `WebAuthn <https://www.w3.org/TR/webauthn/>`_
 * `PyWARP <https://github.com/pyauth/pywarp>`_
 
+Versioning
+~~~~~~~~~~
+This package follows the `Semantic Versioning 2.0.0 <http://semver.org/>`_ 
standard. To control changes, it is
+recommended that application developers pin the package version and manage it 
using `pip-tools
+<https://github.com/jazzband/pip-tools>`_ or similar. For library developers, 
pinning the major version is
+recommended.
+
 .. image:: https://github.com/pyauth/pyotp/workflows/Python%20package/badge.svg
         :target: https://github.com/pyauth/pyotp/actions
 .. image:: https://img.shields.io/codecov/c/github/pyauth/pyotp/master.svg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/README.rst new/pyotp-2.9.0/README.rst
--- old/pyotp-2.8.0/README.rst  2022-12-14 04:34:30.000000000 +0100
+++ new/pyotp-2.9.0/README.rst  2023-06-16 02:22:25.000000000 +0200
@@ -18,19 +18,21 @@
 
 - Ensure transport confidentiality by using HTTPS
 - Ensure HOTP/TOTP secret confidentiality by storing secrets in a controlled 
access database
-- Deny replay attacks by rejecting one-time passwords that have been used by 
the client (this requires storing the most 
-  recently authenticated timestamp, OTP, or hash of the OTP in your database, 
and rejecting the OTP when a match is seen)
+- Deny replay attacks by rejecting one-time passwords that have been used by 
the client (this requires storing the most
+  recently authenticated timestamp, OTP, or hash of the OTP in your database, 
and rejecting the OTP when a match is
+  seen)
 - Throttle (rate limit) brute-force attacks against your application's login 
functionality (see RFC 4226, section 7.3)
 - When implementing a "greenfield" application, consider supporting
   `FIDO U2F <https://en.wikipedia.org/wiki/Universal_2nd_Factor>`_/`WebAuthn 
<https://www.w3.org/TR/webauthn/>`_ in
   addition to HOTP/TOTP. U2F uses asymmetric cryptography to avoid using a 
shared secret design, which strengthens your
   MFA solution against server-side attacks. Hardware U2F also sequesters the 
client secret in a dedicated single-purpose
   device, which strengthens your clients against client-side attacks. And by 
automating scoping of credentials to
-  relying party IDs (application origin/domain names), U2F adds protection 
against phishing attacks. One implementation of
-  FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP 
<https://github.com/pyauth/pywarp>`_.
+  relying party IDs (application origin/domain names), U2F adds protection 
against phishing attacks. One implementation
+  of FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP 
<https://github.com/pyauth/pywarp>`_.
 
 We also recommend that implementers read the
-`OWASP Authentication Cheat Sheet 
<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_
 and
+`OWASP Authentication Cheat Sheet
+<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_
 and
 `NIST SP 800-63-3: Digital Authentication Guideline 
<https://pages.nist.gov/800-63-3/>`_ for a high level overview of
 authentication best practices.
 
@@ -39,8 +41,10 @@
 
 * OTPs involve a shared secret, stored both on the phone and the server
 * OTPs can be generated on a phone without internet connectivity
-* OTPs should always be used as a second factor of authentication (if your 
phone is lost, you account is still secured with a password)
-* Google Authenticator and other OTP client apps allow you to store multiple 
OTP secrets and provision those using a QR Code
+* OTPs should always be used as a second factor of authentication (if your 
phone is lost, you account is still secured
+  with a password)
+* Google Authenticator and other OTP client apps allow you to store multiple 
OTP secrets and provision those using a QR
+  Code
 
 Installation
 ------------
@@ -83,7 +87,8 @@
 
 Generating a Secret Key
 ~~~~~~~~~~~~~~~~~~~~~~~
-A helper function is provided to generate a 32-character base32 secret, 
compatible with Google Authenticator and other OTP apps::
+A helper function is provided to generate a 32-character base32 secret, 
compatible with Google Authenticator and other
+OTP apps::
 
     pyotp.random_base32()
 
@@ -94,8 +99,8 @@
 Google Authenticator Compatible
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-PyOTP works with the Google Authenticator iPhone and Android app, as well as 
other OTP apps like Authy. PyOTP includes the
-ability to generate provisioning URIs for use with the QR Code scanner built 
into these MFA client apps::
+PyOTP works with the Google Authenticator iPhone and Android app, as well as 
other OTP apps like Authy. PyOTP includes
+the ability to generate provisioning URIs for use with the QR Code scanner 
built into these MFA client apps::
 
     
pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name='al...@google.com', 
issuer_name='Secure App')
 
@@ -105,8 +110,8 @@
 
     >>> 
'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
 
-This URL can then be rendered as a QR Code (for example, using 
https://github.com/soldair/node-qrcode) which can then be scanned
-and added to the users list of OTP credentials.
+This URL can then be rendered as a QR Code (for example, using 
https://github.com/soldair/node-qrcode) which can then be
+scanned and added to the users list of OTP credentials.
 
 Parsing these URLs is also supported::
 
@@ -131,6 +136,13 @@
     totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
     print("Current OTP:", totp.now())
 
+Third-party contributions
+~~~~~~~~~~~~~~~~~~~~~~~~~
+The following third-party contributions are not described by a standard, not 
officially supported, and provided for
+reference only:
+
+* ``pyotp.contrib.Steam()``: An implementation of Steam TOTP. Uses the same 
API as `pyotp.TOTP()`.
+
 Links
 ~~~~~
 
@@ -150,6 +162,13 @@
 * `WebAuthn <https://www.w3.org/TR/webauthn/>`_
 * `PyWARP <https://github.com/pyauth/pywarp>`_
 
+Versioning
+~~~~~~~~~~
+This package follows the `Semantic Versioning 2.0.0 <http://semver.org/>`_ 
standard. To control changes, it is
+recommended that application developers pin the package version and manage it 
using `pip-tools
+<https://github.com/jazzband/pip-tools>`_ or similar. For library developers, 
pinning the major version is
+recommended.
+
 .. image:: https://github.com/pyauth/pyotp/workflows/Python%20package/badge.svg
         :target: https://github.com/pyauth/pyotp/actions
 .. image:: https://img.shields.io/codecov/c/github/pyauth/pyotp/master.svg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/pyproject.toml 
new/pyotp-2.9.0/pyproject.toml
--- old/pyotp-2.8.0/pyproject.toml      2022-09-11 20:53:33.000000000 +0200
+++ new/pyotp-2.9.0/pyproject.toml      2023-06-16 02:22:25.000000000 +0200
@@ -1,5 +1,9 @@
 [tool.black]
 line-length = 120
+
 [tool.isort]
 profile = "black"
 line_length = 120
+
+[tool.ruff]
+line-length = 120
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/setup.cfg new/pyotp-2.9.0/setup.cfg
--- old/pyotp-2.8.0/setup.cfg   2022-12-14 04:54:21.051077800 +0100
+++ new/pyotp-2.9.0/setup.cfg   2023-07-28 01:40:52.160089700 +0200
@@ -1,7 +1,3 @@
-[flake8]
-max-line-length = 120
-extend-ignore = E203
-
 [egg_info]
 tag_build = 
 tag_date = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/setup.py new/pyotp-2.9.0/setup.py
--- old/pyotp-2.8.0/setup.py    2022-12-14 04:53:16.000000000 +0100
+++ new/pyotp-2.9.0/setup.py    2023-07-28 01:38:36.000000000 +0200
@@ -4,8 +4,14 @@
 
 setup(
     name="pyotp",
-    version="2.8.0",
+    version="2.9.0",
     url="https://github.com/pyotp/pyotp";,
+    project_urls={
+        "Documentation": "https://pyauth.github.io/pyotp";,
+        "Source Code": "https://github.com/pyauth/pyotp";,
+        "Issue Tracker": "https://github.com/pyauth/pyotp/issues";,
+        "Change Log": 
"https://github.com/pyauth/pyotp/blob/master/Changes.rst";,
+    },
     license="MIT License",
     author="PyOTP contributors",
     author_email="kisl...@gmail.com",
@@ -13,6 +19,9 @@
     long_description=open("README.rst").read(),
     python_requires=">=3.7",
     install_requires=[],
+    extras_require={
+        "test": ["coverage", "wheel", "ruff", "mypy"],
+    },
     packages=["pyotp", "pyotp.contrib"],
     package_dir={"": "src"},
     package_data={"pyotp": ["py.typed"]},
@@ -26,11 +35,11 @@
         "Operating System :: POSIX",
         "Programming Language :: Python",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
         "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
         "Topic :: Software Development :: Libraries :: Python Modules",
     ],
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/src/pyotp/__init__.py 
new/pyotp-2.9.0/src/pyotp/__init__.py
--- old/pyotp-2.8.0/src/pyotp/__init__.py       2022-12-14 04:51:41.000000000 
+0100
+++ new/pyotp-2.9.0/src/pyotp/__init__.py       2023-07-28 01:37:40.000000000 
+0200
@@ -40,6 +40,12 @@
     # Secret (to be filled in later)
     secret = None
 
+    # Encoder (to be filled in later)
+    encoder = None
+
+    # Digits (to be filled in later)
+    digits = None
+
     # Data we'll parse to the correct constructor
     otp_data: Dict[str, Any] = {}
 
@@ -74,10 +80,10 @@
                 otp_data["digest"] = hashlib.sha512
             else:
                 raise ValueError("Invalid value for algorithm, must be SHA1, 
SHA256 or SHA512")
+        elif key == "encoder":
+            encoder = value
         elif key == "digits":
             digits = int(value)
-            if digits not in [6, 7, 8]:
-                raise ValueError("Digits may only be 6, 7, or 8")
             otp_data["digits"] = digits
         elif key == "period":
             otp_data["interval"] = int(value)
@@ -85,11 +91,17 @@
             otp_data["initial_count"] = int(value)
         elif key != "image":
             raise ValueError("{} is not a valid parameter".format(key))
-
+    
+    if encoder != "steam":
+        if digits is not None and digits not in [6, 7, 8]:
+            raise ValueError("Digits may only be 6, 7, or 8")
+    
     if not secret:
         raise ValueError("No secret found in URI")
 
     # Create objects
+    if encoder == "steam":
+        return contrib.Steam(secret, **otp_data)
     if parsed_uri.netloc == "totp":
         return TOTP(secret, **otp_data)
     elif parsed_uri.netloc == "hotp":
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/src/pyotp/contrib/steam.py 
new/pyotp-2.9.0/src/pyotp/contrib/steam.py
--- old/pyotp-2.8.0/src/pyotp/contrib/steam.py  2022-09-11 20:53:33.000000000 
+0200
+++ new/pyotp-2.9.0/src/pyotp/contrib/steam.py  2023-07-28 01:37:40.000000000 
+0200
@@ -12,7 +12,14 @@
     Steam's custom TOTP. Subclass of `pyotp.totp.TOTP`.
     """
 
-    def __init__(self, s: str, name: Optional[str] = None, issuer: 
Optional[str] = None, interval: int = 30) -> None:
+    def __init__(
+        self,
+        s: str,
+        name: Optional[str] = None,
+        issuer: Optional[str] = None,
+        interval: int = 30,
+        digits: int = 5
+    ) -> None:
         """
         :param s: secret in base32 format
         :param interval: the time interval in seconds for OTP. This defaults 
to 30.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/src/pyotp.egg-info/PKG-INFO 
new/pyotp-2.9.0/src/pyotp.egg-info/PKG-INFO
--- old/pyotp-2.8.0/src/pyotp.egg-info/PKG-INFO 2022-12-14 04:54:21.000000000 
+0100
+++ new/pyotp-2.9.0/src/pyotp.egg-info/PKG-INFO 2023-07-28 01:40:52.000000000 
+0200
@@ -1,11 +1,15 @@
 Metadata-Version: 2.1
 Name: pyotp
-Version: 2.8.0
+Version: 2.9.0
 Summary: Python One Time Password Library
 Home-page: https://github.com/pyotp/pyotp
 Author: PyOTP contributors
 Author-email: kisl...@gmail.com
 License: MIT License
+Project-URL: Documentation, https://pyauth.github.io/pyotp
+Project-URL: Source Code, https://github.com/pyauth/pyotp
+Project-URL: Issue Tracker, https://github.com/pyauth/pyotp/issues
+Project-URL: Change Log, 
https://github.com/pyauth/pyotp/blob/master/Changes.rst
 Platform: MacOS X
 Platform: Posix
 Classifier: Intended Audience :: Developers
@@ -14,13 +18,14 @@
 Classifier: Operating System :: POSIX
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Requires-Python: >=3.7
+Provides-Extra: test
 License-File: LICENSE
 
 PyOTP - The Python One-Time Password Library
@@ -43,19 +48,21 @@
 
 - Ensure transport confidentiality by using HTTPS
 - Ensure HOTP/TOTP secret confidentiality by storing secrets in a controlled 
access database
-- Deny replay attacks by rejecting one-time passwords that have been used by 
the client (this requires storing the most 
-  recently authenticated timestamp, OTP, or hash of the OTP in your database, 
and rejecting the OTP when a match is seen)
+- Deny replay attacks by rejecting one-time passwords that have been used by 
the client (this requires storing the most
+  recently authenticated timestamp, OTP, or hash of the OTP in your database, 
and rejecting the OTP when a match is
+  seen)
 - Throttle (rate limit) brute-force attacks against your application's login 
functionality (see RFC 4226, section 7.3)
 - When implementing a "greenfield" application, consider supporting
   `FIDO U2F <https://en.wikipedia.org/wiki/Universal_2nd_Factor>`_/`WebAuthn 
<https://www.w3.org/TR/webauthn/>`_ in
   addition to HOTP/TOTP. U2F uses asymmetric cryptography to avoid using a 
shared secret design, which strengthens your
   MFA solution against server-side attacks. Hardware U2F also sequesters the 
client secret in a dedicated single-purpose
   device, which strengthens your clients against client-side attacks. And by 
automating scoping of credentials to
-  relying party IDs (application origin/domain names), U2F adds protection 
against phishing attacks. One implementation of
-  FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP 
<https://github.com/pyauth/pywarp>`_.
+  relying party IDs (application origin/domain names), U2F adds protection 
against phishing attacks. One implementation
+  of FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP 
<https://github.com/pyauth/pywarp>`_.
 
 We also recommend that implementers read the
-`OWASP Authentication Cheat Sheet 
<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_
 and
+`OWASP Authentication Cheat Sheet
+<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_
 and
 `NIST SP 800-63-3: Digital Authentication Guideline 
<https://pages.nist.gov/800-63-3/>`_ for a high level overview of
 authentication best practices.
 
@@ -64,8 +71,10 @@
 
 * OTPs involve a shared secret, stored both on the phone and the server
 * OTPs can be generated on a phone without internet connectivity
-* OTPs should always be used as a second factor of authentication (if your 
phone is lost, you account is still secured with a password)
-* Google Authenticator and other OTP client apps allow you to store multiple 
OTP secrets and provision those using a QR Code
+* OTPs should always be used as a second factor of authentication (if your 
phone is lost, you account is still secured
+  with a password)
+* Google Authenticator and other OTP client apps allow you to store multiple 
OTP secrets and provision those using a QR
+  Code
 
 Installation
 ------------
@@ -108,7 +117,8 @@
 
 Generating a Secret Key
 ~~~~~~~~~~~~~~~~~~~~~~~
-A helper function is provided to generate a 32-character base32 secret, 
compatible with Google Authenticator and other OTP apps::
+A helper function is provided to generate a 32-character base32 secret, 
compatible with Google Authenticator and other
+OTP apps::
 
     pyotp.random_base32()
 
@@ -119,8 +129,8 @@
 Google Authenticator Compatible
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-PyOTP works with the Google Authenticator iPhone and Android app, as well as 
other OTP apps like Authy. PyOTP includes the
-ability to generate provisioning URIs for use with the QR Code scanner built 
into these MFA client apps::
+PyOTP works with the Google Authenticator iPhone and Android app, as well as 
other OTP apps like Authy. PyOTP includes
+the ability to generate provisioning URIs for use with the QR Code scanner 
built into these MFA client apps::
 
     
pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name='al...@google.com', 
issuer_name='Secure App')
 
@@ -130,8 +140,8 @@
 
     >>> 
'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
 
-This URL can then be rendered as a QR Code (for example, using 
https://github.com/soldair/node-qrcode) which can then be scanned
-and added to the users list of OTP credentials.
+This URL can then be rendered as a QR Code (for example, using 
https://github.com/soldair/node-qrcode) which can then be
+scanned and added to the users list of OTP credentials.
 
 Parsing these URLs is also supported::
 
@@ -156,6 +166,13 @@
     totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
     print("Current OTP:", totp.now())
 
+Third-party contributions
+~~~~~~~~~~~~~~~~~~~~~~~~~
+The following third-party contributions are not described by a standard, not 
officially supported, and provided for
+reference only:
+
+* ``pyotp.contrib.Steam()``: An implementation of Steam TOTP. Uses the same 
API as `pyotp.TOTP()`.
+
 Links
 ~~~~~
 
@@ -175,6 +192,13 @@
 * `WebAuthn <https://www.w3.org/TR/webauthn/>`_
 * `PyWARP <https://github.com/pyauth/pywarp>`_
 
+Versioning
+~~~~~~~~~~
+This package follows the `Semantic Versioning 2.0.0 <http://semver.org/>`_ 
standard. To control changes, it is
+recommended that application developers pin the package version and manage it 
using `pip-tools
+<https://github.com/jazzband/pip-tools>`_ or similar. For library developers, 
pinning the major version is
+recommended.
+
 .. image:: https://github.com/pyauth/pyotp/workflows/Python%20package/badge.svg
         :target: https://github.com/pyauth/pyotp/actions
 .. image:: https://img.shields.io/codecov/c/github/pyauth/pyotp/master.svg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/src/pyotp.egg-info/SOURCES.txt 
new/pyotp-2.9.0/src/pyotp.egg-info/SOURCES.txt
--- old/pyotp-2.8.0/src/pyotp.egg-info/SOURCES.txt      2022-12-14 
04:54:21.000000000 +0100
+++ new/pyotp-2.9.0/src/pyotp.egg-info/SOURCES.txt      2023-07-28 
01:40:52.000000000 +0200
@@ -2,7 +2,6 @@
 MANIFEST.in
 README.rst
 pyproject.toml
-setup.cfg
 setup.py
 test.py
 src/pyotp/__init__.py
@@ -16,6 +15,7 @@
 src/pyotp.egg-info/SOURCES.txt
 src/pyotp.egg-info/dependency_links.txt
 src/pyotp.egg-info/not-zip-safe
+src/pyotp.egg-info/requires.txt
 src/pyotp.egg-info/top_level.txt
 src/pyotp/contrib/__init__.py
 src/pyotp/contrib/steam.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/src/pyotp.egg-info/requires.txt 
new/pyotp-2.9.0/src/pyotp.egg-info/requires.txt
--- old/pyotp-2.8.0/src/pyotp.egg-info/requires.txt     1970-01-01 
01:00:00.000000000 +0100
+++ new/pyotp-2.9.0/src/pyotp.egg-info/requires.txt     2023-07-28 
01:40:52.000000000 +0200
@@ -0,0 +1,6 @@
+
+[test]
+coverage
+wheel
+ruff
+mypy
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyotp-2.8.0/test.py new/pyotp-2.9.0/test.py
--- old/pyotp-2.8.0/test.py     2022-09-11 20:53:33.000000000 +0200
+++ new/pyotp-2.9.0/test.py     2023-07-28 01:37:40.000000000 +0200
@@ -364,6 +364,13 @@
         with self.assertRaises(ValueError) as cm:
             pyotp.parse_uri("otpauth://totp?algorithm=aes")
         self.assertEqual("Invalid value for algorithm, must be SHA1, SHA256 or 
SHA512", str(cm.exception))
+    
+    def test_parse_steam(self):
+        otp = 
pyotp.parse_uri("otpauth://totp/Steam:?secret=SOME_SECRET&encoder=steam")
+        self.assertEqual(type(otp), pyotp.contrib.Steam)
+
+        otp = pyotp.parse_uri("otpauth://totp/Steam:?secret=SOME_SECRET")
+        self.assertNotEqual(type(otp), pyotp.contrib.Steam)
 
     @unittest.skipIf(sys.version_info < (3, 6), "Skipping test that requires 
deterministic dict key enumeration")
     def test_algorithms(self):
@@ -420,6 +427,20 @@
         otp = pyotp.parse_uri(otp.provisioning_uri(name="n", issuer_name="i", 
image="https://test.net/test.png";))
         self.assertEqual(hashlib.sha512, otp.digest)
 
+        otp = 
pyotp.parse_uri("otpauth://totp/Steam:?secret=FMXNK4QEGKVPULRTADY6JIDK5VHUBGZW&encoder=steam")
+        self.assertEqual(type(otp), pyotp.contrib.Steam)
+        self.assertEqual(otp.at(0), "C5V56")
+        self.assertEqual(otp.at(30), "QJY8Y")
+        self.assertEqual(otp.at(60), "R3WQY")
+        self.assertEqual(otp.at(90), "JG3T3")
+
+        # period and digits should be ignored
+        otp = 
pyotp.parse_uri("otpauth://totp/Steam:?secret=FMXNK4QEGKVPULRTADY6JIDK5VHUBGZW&period=15&digits=7&encoder=steam")
+        self.assertEqual(type(otp), pyotp.contrib.Steam)
+        self.assertEqual(otp.at(0), "C5V56")
+        self.assertEqual(otp.at(30), "QJY8Y")
+        self.assertEqual(otp.at(60), "R3WQY")
+        self.assertEqual(otp.at(90), "JG3T3")
 
 class Timecop(object):
     """

Reply via email to