I am implementing a forgot password feature on my application. In order to generate the otp code I have used the pyotp library. The code generates the otp code but when I try to reset the password using the generated password, it shows the error that "the verification code has beeen expired" but the otp code here has the expiry time of 180 seconds. I think the problem is in the way I am generating the secret key. I guess the reset password method expects the same key that was used while creating the otp code. I want to resend the new code everytime the user hits the endpoint, even if the code is valid. How can I pass the same secret key to the reset password method? Here is my implementation code:
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret, interval=settings.verification_code_expire_time)
verification_code = totp.now()
if not user.code:
user.code = VerificationCode(code=verification_code)
else:
user.code.code = verification_code
await db.commit()
return verification_code
query = select(VerificationCode).where(VerificationCode.code == reset_password_schema.verification_code)
result = await db.execute(query)
verification_code = result.scalar_one_or_none()
if not verification_code:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid verification code.")
totp = pyotp.TOTP(pyotp.random_base32(), interval=settings.verification_code_expire_time)
if not totp.verify(reset_password_schema.verification_code):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Verification code has been expired.")
query = (
update(User).where(User.id == verification_code.user_id).values(
password=reset_password_schema.new_password)
)
You generate new secret key at your Method to reset password
, that's why this happens.
Instead, you should use secret key from created VerificationCode. Here is simple example:
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret,interval=settings.verification_code_expire_time)
verification_code = totp.now()
if not user.code:
user.code = VerificationCode(code=verification_code, secret=secret)
else:
user.code.code = verification_code
user.code.secret = secret # SAVE THIS ENCRYPTED
await db.commit()
return verification_code
P.S. You really should store this secret encrypted
Method to reset password
:query = select(VerificationCode).where(VerificationCode.code == reset_password_schema.verification_code)
result = await db.execute(query)
verification_code_entry= result.scalar_one_or_none()
if not verification_code_entry:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,detail="Invalid verification code.")
# HERE IS MAIN CHANGE
secret = verification_code_entry.secret
totp= pyotp.TOTP(secret,interval=settings.verification_code_expire_time)
if not totp.verify(reset_password_schema.verification_code):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Verification code has expied.")
query = (
update(User)
.where(User.id == verification_code_entry.user_id)
.values(password=reset_password_schema.new_password)
)
await db.execute(query)
await db.commit()