Source code for authnzerver.tokens

# -*- coding: utf-8 -*-

"""This module handles generation of various tokens.

"""

#############
## LOGGING ##
#############

import logging
from typing import Sequence, Union

LOGGER = logging.getLogger(__name__)


#############
## IMPORTS ##
#############

import json
from datetime import datetime
from secrets import token_urlsafe

from .jsonencoder import FrontendEncoder

# this replaces the default encoder and makes it so Tornado will do the right
# thing when it converts dicts to JSON when a
# tornado.web.RequestHandler.write(dict) is called.
json._default_encoder = FrontendEncoder()


################################
## ENCRYPTION RELATED IMPORTS ##
################################

from .messaging import encrypt_message, decrypt_message


##################################################
## USER VERIFICATION AND FORGOT PASSWORD TOKENS ##
##################################################


[docs]def generate_email_token( ip_address: str, user_agent: str, email_address: str, session_token: str, session_cookie_key: bytes, ) -> bytes: """This generates a token useful for verifying email addresses. Also used for forgot-password emails. This encodes the user's IP address, user agent, email address, and session token into the token generated. The token is encrypted using the Fernet scheme and the session cookie (the key used to sign the frontend's cookies) to keep things simple. """ token_payload = { "iat": datetime.utcnow(), "ipa": ip_address, "usa": user_agent, "ema": email_address, "stk": session_token, "tid": token_urlsafe(16), } return encrypt_message(token_payload, session_cookie_key)
[docs]def verify_email_token( token: bytes, ip_address: str, user_agent: str, session_token: str, email_address: str, session_cookie_key: bytes, match_returned_items: Sequence = ("ipa", "ema"), ttl_seconds: int = 900, reqid: Union[int, str] = None, ) -> bool: """This verifies the token returned by the user. By default, it requires that the token be returned no more than 15 minutes after it's been issued. It also tries to match the specified items in ``match_returned_items`` to the current values provided as args:: 'ipa' -> ip_address 'usa' -> user_agent 'stk' -> session_token 'ema' -> email_address """ decrypted_token = decrypt_message( token, session_cookie_key, ttl=ttl_seconds, reqid=reqid, ) # if the decryption or TTL test fails, return False if decrypted_token is None: return False # otherwise, check the items in the token itself if isinstance(match_returned_items, (tuple, list)): if ( "ipa" in match_returned_items and ip_address != decrypted_token["ipa"] ): return False if ( "ema" in match_returned_items and email_address != decrypted_token["ema"] ): return False if ( "usa" in match_returned_items and user_agent != decrypted_token["usa"] ): return False if ( "stk" in match_returned_items and session_token != decrypted_token["stk"] ): return False # if we make it to here, all is well return True