text: # -*- coding: utf-8 -*-
  """
      flask_security.totp
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  
      Flask-Security TOTP (Timed-One-Time-Passwords) module
  
      :copyright: (c) 2019 by J. Christopher Wagner (jwag).
      :license: MIT, see LICENSE for more details.
  """
  
  from passlib.totp import TOTP, TokenError
  
  
  class Totp(object):
      """ Encapsulate usage of Passlib TOTP functionality.
  
      Flask-Security doesn't implement any replay-attack protection out of the box
      as suggested by:
      https://passlib.readthedocs.io/en/stable/narr/totp-tutorial.html#match-verify
  
      Subclass this and implement the get/set last_counter methods. Your subclass can
      be registered at Flask-Security creation/initialization time.
  
      .. versionadded:: 3.4.0
  
      """
  
      def __init__(self, secrets, issuer):
          """ Initialize a totp factory.
          secrets are used to encrypt the per-user totp_secret on disk.
          """
          # This should be a dict with at least one entry
          if not isinstance(secrets, dict) or len(secrets) < 1:
              raise ValueError("secrets needs to be a dict with at least one entry")
          self._totp = TOTP.using(issuer=issuer, secrets=secrets)
  
      def generate_totp_password(self, totp_secret):
          """Get time-based one-time password on the basis of given secret and time
          :param totp_secret: the unique shared secret of the user
          """
          return self._totp.from_source(totp_secret).generate().token
  
      def generate_totp_secret(self):
          """ Create new user-unique totp_secret.
  
          We return an encrypted json string so that when sent in a cookie or
          sent to DB - it is encrypted.
  
          """
          return self._totp.new().to_json(encrypt=True)
  
      def verify_totp(self, token, totp_secret, user, window=0):
          """ Verifies token for specific user.
  
          :param token: token to be check against user's secret
          :param totp_secret: the unique shared secret of the user
          :param user: User model
          :param window: optional. How far backward and forward in time to search
           for a match. Measured in seconds.
          :return: True if match
          """
  
          # TODO - in old implementation  using onetimepass window was described
          # as 'compensate for clock skew) and 'interval_length' would say how long
          # the token is good for.
          # In passlib - 'window' means how far back and forward to look and 'clock_skew'
          # is specifically for well, clock slew.
          try:
              tmatch = self._totp.verify(
                  token,
                  totp_secret,
                  window=window,
                  last_counter=self.get_last_counter(user),
              )
              self.set_last_counter(user, tmatch)
              return True
  
          except TokenError:
              return False
  
      def get_totp_uri(self, username, totp_secret):
          """ Generate provisioning url for use with the qrcode
                  scanner built into the app
  
          :param username: username/email of the current user
          :param totp_secret: a unique shared secret of the user
          """
          tp = self._totp.from_source(totp_secret)
          return tp.to_uri(username)
  
      def get_last_counter(self, user):
          """ Implement this to fetch stored last_counter from cache.
  
          :param user: User model
          :return: last_counter as stored in set_last_counter()
          """
          return None
  
      def set_last_counter(self, user, tmatch):
          """ Implement this to cache last_counter.
  
          :param user: User model
          :param tmatch: a TotpMatch as returned from totp.verify()
          """
          pass
  