Models
Otp
Model that allows users to store and validate two-factor authentication credentials using the OTP method.
class Otp(TimeStampedModel):
user = models.OneToOneField(User, on_delete=models.CASCADE)
secret = models.CharField(max_length=255)
words = models.TextField(blank=True, null=True)
last_used_otp = models.CharField(max_length=7)
last_otp_timestamp = models.DateTimeField(blank=True, null=True)
@classmethod
def verify_activation(cls, secret, otp):
totp = pyotp.TOTP(secret)
is_valid = True if totp.now() == otp else False
return is_valid
@classmethod
def generate_random_config(cls, email):
secret = pyotp.random_base32()
provisioning_uri = pyotp.totp.TOTP(secret).provisioning_uri(
name=email, issuer_name=app_settings.OTP_APP_DISPLAY_NAME
)
return {"secret": secret, "provisioning_uri": provisioning_uri}
@classmethod
def generate_config(cls, email, secret):
provisioning_uri = pyotp.totp.TOTP(secret).provisioning_uri(
name=email, issuer_name=app_settings.OTP_APP_DISPLAY_NAME
)
return {"secret": secret, "provisioning_uri": provisioning_uri}
@classmethod
def create(cls, email, secret):
UserModel = User
user = UserModel.objects.get(email=email)
# Encrypt Secret before storing it in the database
key = bytes(app_settings.OTP_SECRET_KEY, "utf-8")
f = Fernet(key)
secret = bytes(secret, "utf-8")
secret_token = f.encrypt(secret)
otp_config = cls()
otp_config.secret = secret_token.decode("utf-8")
otp_config.user = user
otp_config.save()
return otp_config
def verify_otp(self, otp):
# Decrypt Secret before using it
key = bytes(app_settings.OTP_SECRET_KEY, "utf-8")
f = Fernet(key)
secret = f.decrypt(bytes(self.secret, "utf-8"))
totp = pyotp.TOTP(secret)
is_valid = True if totp.now() == otp else False
replay_attempt = self.verify_replay_attack(otp=otp)
if is_valid and not replay_attempt:
self.last_used_otp = otp
self.last_otp_timestamp = timezone.now()
self.save()
return is_valid and not replay_attempt
def verify_replay_attack(self, otp):
# To mitigate replay attacks we don't allow to reuse the same otp for a window of time after using it
replay_attempt = False
if otp == self.last_used_otp:
now = timezone.now()
time_window = now - self.last_otp_timestamp
if time_window.seconds < 60:
replay_attempt = True
return replay_attempt
@classmethod
def save_mnemonic_words(cls, email, words):
key = bytes(app_settings.OTP_SECRET_KEY, "utf-8")
f = Fernet(key)
words = bytes(words.replace(" ", ""), "utf-8")
words_token = f.encrypt(words)
otp = Otp.objects.get(user__email=email)
otp.words = words_token.decode("utf-8")
otp.save()
return otp
def verify_mnemonic_words(self, words):
key = bytes(app_settings.OTP_SECRET_KEY, "utf-8")
f = Fernet(key)
otp_words = f.decrypt(bytes(self.words, "utf-8")).decode("utf-8")
is_valid = True if otp_words == words.replace(" ", "") else False
return is_valid
The main fields are:
user: Relationship with the CustomUser model.
secret: Stores the value of a random secret generated in the generate_random_config method.
words: Field that stores the user’s recovery words.
last_used_otp: Field that records the last otp code used to avoid the replay attack.
last_otp_timestamp: Field that records the timestamp of the last code entered, this is used to avoid the replay attack.
The methods of this model are:
verify_activation: Method used to activate the OTP method in a user. For this you must enter the secret generated for the user and the otp code. This method returns a boolean value that depends on the verification and validation of the entered code. It does not require to instantiate the model.
generate_random_config: Method to generate the initial configuration of the OTP for the user. It does not require to instantiate the model.
generate_config: Method to obtain the configuration based on the user’s secret and email. It does not require to instantiate the model.
create: Method to register the OTP configuration of a user in the model. It does not require to instantiate the model.
verify_otp: Method to verify the otp code. Requires instantiating the model.
verify_replay_attack: Method that validates that an otp code is not used twice in the life time of the code. Requires instantiating the model.
save_mnemonic_words: Method that stores the recovery words in case the user loses the otp configuration information. It does not require to instantiate the model.
verify_mnemonic_words: Validates the user’s recovery words. Requires instantiating the model.